cleanup validation [#294] [Scaffold] Send Screen - transaction + address inputs (#308) - zec amount refactored to be always Int64 - number formatter to zec string for the UI added - cleaned up the invalid input conditions by providing computed properties - isValidZcashAddress() added [#294] [Scaffold] Send Screen - transaction + address inputs (#308) - prefix input view support implemented - zcash or $ symbol used as a prefix for the amount input - $ computations and max out implemented [#294] [Scaffold] Send Screen - transaction + address inputs (#308) - insufficient funds logic (UI error + inability to send) added - $ balance value visible at send screen - cleanup [#294] [Scaffold] Send Screen - transaction + address inputs (#308) - send routing simplified and cleaned up [#294] [Scaffold] Send Screen - transaction + address inputs (#308) - unit tests [#294] [Scaffold] Send Screen - transaction + address inputs (#308) - cleanup [#294] [Scaffold] Send Screen - transaction + address inputs (#308) - review comments solved
This commit is contained in:
parent
10070c3c02
commit
2bb5451047
|
@ -59,13 +59,13 @@
|
||||||
2E35F99A27B3E99C00EB79CD /* TextFieldTitleAccessoryButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E35F99927B3E99C00EB79CD /* TextFieldTitleAccessoryButtonStyle.swift */; };
|
2E35F99A27B3E99C00EB79CD /* TextFieldTitleAccessoryButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E35F99927B3E99C00EB79CD /* TextFieldTitleAccessoryButtonStyle.swift */; };
|
||||||
2E58E73B274679F000B2B84B /* OnboardingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E58E73A274679F000B2B84B /* OnboardingHeaderView.swift */; };
|
2E58E73B274679F000B2B84B /* OnboardingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E58E73A274679F000B2B84B /* OnboardingHeaderView.swift */; };
|
||||||
2E6CF8DD27D78319004DCD7A /* CurrencySelectionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E6CF8DC27D78319004DCD7A /* CurrencySelectionStore.swift */; };
|
2E6CF8DD27D78319004DCD7A /* CurrencySelectionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E6CF8DC27D78319004DCD7A /* CurrencySelectionStore.swift */; };
|
||||||
2E8719CB27FB09990082C926 /* TransactionTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8719CA27FB09990082C926 /* TransactionTextField.swift */; };
|
2E8719CB27FB09990082C926 /* TransactionAmountTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8719CA27FB09990082C926 /* TransactionAmountTextField.swift */; };
|
||||||
2E8719CD27FB0D3B0082C926 /* TransactionCurrencySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8719CC27FB0D3B0082C926 /* TransactionCurrencySelector.swift */; };
|
2E8719CD27FB0D3B0082C926 /* TransactionCurrencySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8719CC27FB0D3B0082C926 /* TransactionCurrencySelector.swift */; };
|
||||||
2EA11F5B27467EF800709571 /* OnboardingFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA11F5A27467EF800709571 /* OnboardingFooterView.swift */; };
|
2EA11F5B27467EF800709571 /* OnboardingFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA11F5A27467EF800709571 /* OnboardingFooterView.swift */; };
|
||||||
2EA11F5D27467F7700709571 /* OnboardingContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA11F5C27467F7700709571 /* OnboardingContentView.swift */; };
|
2EA11F5D27467F7700709571 /* OnboardingContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA11F5C27467F7700709571 /* OnboardingContentView.swift */; };
|
||||||
2EB1C5E827D77F6100BC43D7 /* TextFieldStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EB1C5E727D77F6100BC43D7 /* TextFieldStore.swift */; };
|
2EB1C5E827D77F6100BC43D7 /* TextFieldStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EB1C5E727D77F6100BC43D7 /* TextFieldStore.swift */; };
|
||||||
2EB660E02747EAB900A06A07 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E5C03802738C570008BFFD3 /* OnboardingScreen.swift */; };
|
2EB660E02747EAB900A06A07 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E5C03802738C570008BFFD3 /* OnboardingScreen.swift */; };
|
||||||
2EB7758727FC67FD00269373 /* TransactionInputStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EB7758627FC67FD00269373 /* TransactionInputStore.swift */; };
|
2EB7758727FC67FD00269373 /* TransactionAmountInputStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EB7758627FC67FD00269373 /* TransactionAmountInputStore.swift */; };
|
||||||
2EDA07A027EDE18C00D6F09B /* TextFieldInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EDA079F27EDE18C00D6F09B /* TextFieldInput.swift */; };
|
2EDA07A027EDE18C00D6F09B /* TextFieldInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EDA079F27EDE18C00D6F09B /* TextFieldInput.swift */; };
|
||||||
2EDA07A227EDE1AE00D6F09B /* TextFieldFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EDA07A127EDE1AE00D6F09B /* TextFieldFooter.swift */; };
|
2EDA07A227EDE1AE00D6F09B /* TextFieldFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EDA07A127EDE1AE00D6F09B /* TextFieldFooter.swift */; };
|
||||||
2EDA07A427EDE2A900D6F09B /* DebugFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EDA07A327EDE2A900D6F09B /* DebugFrame.swift */; };
|
2EDA07A427EDE2A900D6F09B /* DebugFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EDA07A327EDE2A900D6F09B /* DebugFrame.swift */; };
|
||||||
|
@ -107,6 +107,8 @@
|
||||||
9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF643281FEC9900BA3F17 /* SendTests.swift */; };
|
9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF643281FEC9900BA3F17 /* SendTests.swift */; };
|
||||||
9E5BF6462821028C00BA3F17 /* WrappedUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF6452821028C00BA3F17 /* WrappedUserDefaults.swift */; };
|
9E5BF6462821028C00BA3F17 /* WrappedUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF6452821028C00BA3F17 /* WrappedUserDefaults.swift */; };
|
||||||
9E5BF648282277BE00BA3F17 /* WrappedNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF647282277BE00BA3F17 /* WrappedNotificationCenter.swift */; };
|
9E5BF648282277BE00BA3F17 /* WrappedNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF647282277BE00BA3F17 /* WrappedNotificationCenter.swift */; };
|
||||||
|
9E5BF64F2823E94900BA3F17 /* TransactionAddressTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF64D2823E94900BA3F17 /* TransactionAddressTextField.swift */; };
|
||||||
|
9E5BF6502823E94900BA3F17 /* TransactionAddressInputStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E5BF64E2823E94900BA3F17 /* TransactionAddressInputStore.swift */; };
|
||||||
9E69A24D27FB002800A55317 /* Welcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* Welcome.swift */; };
|
9E69A24D27FB002800A55317 /* Welcome.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E69A24C27FB002800A55317 /* Welcome.swift */; };
|
||||||
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */; };
|
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */; };
|
||||||
9EAFEB822805793200199FC9 /* AppReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB812805793200199FC9 /* AppReducerTests.swift */; };
|
9EAFEB822805793200199FC9 /* AppReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB812805793200199FC9 /* AppReducerTests.swift */; };
|
||||||
|
@ -120,6 +122,10 @@
|
||||||
9EAFEB9228081E9400199FC9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93874EF273C4DE200F0E875 /* HomeView.swift */; };
|
9EAFEB9228081E9400199FC9 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93874EF273C4DE200F0E875 /* HomeView.swift */; };
|
||||||
9EBEF87A27CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */; };
|
9EBEF87A27CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */; };
|
||||||
9ECAE56827FC713C0089A0EF /* DatabaseFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ECAE56727FC713C0089A0EF /* DatabaseFiles.swift */; };
|
9ECAE56827FC713C0089A0EF /* DatabaseFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9ECAE56727FC713C0089A0EF /* DatabaseFiles.swift */; };
|
||||||
|
9EDDEA8C28250F9C00B4100C /* Double+Zcash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EDDEA8B28250F9C00B4100C /* Double+Zcash.swift */; };
|
||||||
|
9EDDEAA22829610D00B4100C /* CurrencySelectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EDDEA9F2829610D00B4100C /* CurrencySelectionTests.swift */; };
|
||||||
|
9EDDEAA32829610D00B4100C /* TransactionAmountInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EDDEAA02829610D00B4100C /* TransactionAmountInputTests.swift */; };
|
||||||
|
9EDDEAA42829610D00B4100C /* TransactionAddressInputTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EDDEAA12829610D00B4100C /* TransactionAddressInputTests.swift */; };
|
||||||
9EF8135C27ECC25E0075AF48 /* WalletStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */; };
|
9EF8135C27ECC25E0075AF48 /* WalletStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */; };
|
||||||
9EF8135D27ECC25E0075AF48 /* UserPreferencesStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */; };
|
9EF8135D27ECC25E0075AF48 /* UserPreferencesStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */; };
|
||||||
9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135F27F043CC0075AF48 /* AppDelegate.swift */; };
|
9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135F27F043CC0075AF48 /* AppDelegate.swift */; };
|
||||||
|
@ -231,12 +237,12 @@
|
||||||
2E58E73A274679F000B2B84B /* OnboardingHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingHeaderView.swift; sourceTree = "<group>"; };
|
2E58E73A274679F000B2B84B /* OnboardingHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingHeaderView.swift; sourceTree = "<group>"; };
|
||||||
2E5C03802738C570008BFFD3 /* OnboardingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreen.swift; sourceTree = "<group>"; };
|
2E5C03802738C570008BFFD3 /* OnboardingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreen.swift; sourceTree = "<group>"; };
|
||||||
2E6CF8DC27D78319004DCD7A /* CurrencySelectionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencySelectionStore.swift; sourceTree = "<group>"; };
|
2E6CF8DC27D78319004DCD7A /* CurrencySelectionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencySelectionStore.swift; sourceTree = "<group>"; };
|
||||||
2E8719CA27FB09990082C926 /* TransactionTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionTextField.swift; sourceTree = "<group>"; };
|
2E8719CA27FB09990082C926 /* TransactionAmountTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionAmountTextField.swift; sourceTree = "<group>"; };
|
||||||
2E8719CC27FB0D3B0082C926 /* TransactionCurrencySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionCurrencySelector.swift; sourceTree = "<group>"; };
|
2E8719CC27FB0D3B0082C926 /* TransactionCurrencySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionCurrencySelector.swift; sourceTree = "<group>"; };
|
||||||
2EA11F5A27467EF800709571 /* OnboardingFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingFooterView.swift; sourceTree = "<group>"; };
|
2EA11F5A27467EF800709571 /* OnboardingFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingFooterView.swift; sourceTree = "<group>"; };
|
||||||
2EA11F5C27467F7700709571 /* OnboardingContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingContentView.swift; sourceTree = "<group>"; };
|
2EA11F5C27467F7700709571 /* OnboardingContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingContentView.swift; sourceTree = "<group>"; };
|
||||||
2EB1C5E727D77F6100BC43D7 /* TextFieldStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldStore.swift; sourceTree = "<group>"; };
|
2EB1C5E727D77F6100BC43D7 /* TextFieldStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldStore.swift; sourceTree = "<group>"; };
|
||||||
2EB7758627FC67FD00269373 /* TransactionInputStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionInputStore.swift; sourceTree = "<group>"; };
|
2EB7758627FC67FD00269373 /* TransactionAmountInputStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionAmountInputStore.swift; sourceTree = "<group>"; };
|
||||||
2EDA079F27EDE18C00D6F09B /* TextFieldInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldInput.swift; sourceTree = "<group>"; };
|
2EDA079F27EDE18C00D6F09B /* TextFieldInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldInput.swift; sourceTree = "<group>"; };
|
||||||
2EDA07A127EDE1AE00D6F09B /* TextFieldFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldFooter.swift; sourceTree = "<group>"; };
|
2EDA07A127EDE1AE00D6F09B /* TextFieldFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldFooter.swift; sourceTree = "<group>"; };
|
||||||
2EDA07A327EDE2A900D6F09B /* DebugFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugFrame.swift; sourceTree = "<group>"; };
|
2EDA07A327EDE2A900D6F09B /* DebugFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugFrame.swift; sourceTree = "<group>"; };
|
||||||
|
@ -276,6 +282,8 @@
|
||||||
9E5BF643281FEC9900BA3F17 /* SendTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTests.swift; sourceTree = "<group>"; };
|
9E5BF643281FEC9900BA3F17 /* SendTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTests.swift; sourceTree = "<group>"; };
|
||||||
9E5BF6452821028C00BA3F17 /* WrappedUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedUserDefaults.swift; sourceTree = "<group>"; };
|
9E5BF6452821028C00BA3F17 /* WrappedUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedUserDefaults.swift; sourceTree = "<group>"; };
|
||||||
9E5BF647282277BE00BA3F17 /* WrappedNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedNotificationCenter.swift; sourceTree = "<group>"; };
|
9E5BF647282277BE00BA3F17 /* WrappedNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedNotificationCenter.swift; sourceTree = "<group>"; };
|
||||||
|
9E5BF64D2823E94900BA3F17 /* TransactionAddressTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressTextField.swift; sourceTree = "<group>"; };
|
||||||
|
9E5BF64E2823E94900BA3F17 /* TransactionAddressInputStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressInputStore.swift; sourceTree = "<group>"; };
|
||||||
9E69A24C27FB002800A55317 /* Welcome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Welcome.swift; sourceTree = "<group>"; };
|
9E69A24C27FB002800A55317 /* Welcome.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Welcome.swift; sourceTree = "<group>"; };
|
||||||
9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorage.swift; sourceTree = "<group>"; };
|
9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorage.swift; sourceTree = "<group>"; };
|
||||||
9EAFEB812805793200199FC9 /* AppReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducerTests.swift; sourceTree = "<group>"; };
|
9EAFEB812805793200199FC9 /* AppReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducerTests.swift; sourceTree = "<group>"; };
|
||||||
|
@ -287,6 +295,10 @@
|
||||||
9EAFEB8E2808183D00199FC9 /* SandboxStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SandboxStore.swift; sourceTree = "<group>"; };
|
9EAFEB8E2808183D00199FC9 /* SandboxStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SandboxStore.swift; sourceTree = "<group>"; };
|
||||||
9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseTestPreambleView.swift; sourceTree = "<group>"; };
|
9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseTestPreambleView.swift; sourceTree = "<group>"; };
|
||||||
9ECAE56727FC713C0089A0EF /* DatabaseFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseFiles.swift; sourceTree = "<group>"; };
|
9ECAE56727FC713C0089A0EF /* DatabaseFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseFiles.swift; sourceTree = "<group>"; };
|
||||||
|
9EDDEA8B28250F9C00B4100C /* Double+Zcash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Zcash.swift"; sourceTree = "<group>"; };
|
||||||
|
9EDDEA9F2829610D00B4100C /* CurrencySelectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CurrencySelectionTests.swift; sourceTree = "<group>"; };
|
||||||
|
9EDDEAA02829610D00B4100C /* TransactionAmountInputTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAmountInputTests.swift; sourceTree = "<group>"; };
|
||||||
|
9EDDEAA12829610D00B4100C /* TransactionAddressInputTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionAddressInputTests.swift; sourceTree = "<group>"; };
|
||||||
9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletStorageTests.swift; sourceTree = "<group>"; };
|
9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletStorageTests.swift; sourceTree = "<group>"; };
|
||||||
9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorageTests.swift; sourceTree = "<group>"; };
|
9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorageTests.swift; sourceTree = "<group>"; };
|
||||||
9EF8135F27F043CC0075AF48 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
9EF8135F27F043CC0075AF48 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
@ -577,6 +589,7 @@
|
||||||
9ECAE56727FC713C0089A0EF /* DatabaseFiles.swift */,
|
9ECAE56727FC713C0089A0EF /* DatabaseFiles.swift */,
|
||||||
9EAFEB892806F48100199FC9 /* ZCashSDKEnvironment.swift */,
|
9EAFEB892806F48100199FC9 /* ZCashSDKEnvironment.swift */,
|
||||||
9E2F1C8128095AFE004E65FE /* Int64+Zcash.swift */,
|
9E2F1C8128095AFE004E65FE /* Int64+Zcash.swift */,
|
||||||
|
9EDDEA8B28250F9C00B4100C /* Double+Zcash.swift */,
|
||||||
9E2F1C832809B606004E65FE /* DebugMenu.swift */,
|
9E2F1C832809B606004E65FE /* DebugMenu.swift */,
|
||||||
);
|
);
|
||||||
path = Util;
|
path = Util;
|
||||||
|
@ -630,10 +643,8 @@
|
||||||
2E35F99027B28E6800EB79CD /* TextFields */ = {
|
2E35F99027B28E6800EB79CD /* TextFields */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
2E8719CA27FB09990082C926 /* TransactionTextField.swift */,
|
9E5BF64C2823E84300BA3F17 /* TransactionAddress */,
|
||||||
2E8719CC27FB0D3B0082C926 /* TransactionCurrencySelector.swift */,
|
9E5BF64B2823C91200BA3F17 /* TransactionAmount */,
|
||||||
2EB7758627FC67FD00269373 /* TransactionInputStore.swift */,
|
|
||||||
2E6CF8DC27D78319004DCD7A /* CurrencySelectionStore.swift */,
|
|
||||||
2EDA07A527EDE31100D6F09B /* Components */,
|
2EDA07A527EDE31100D6F09B /* Components */,
|
||||||
);
|
);
|
||||||
path = TextFields;
|
path = TextFields;
|
||||||
|
@ -653,11 +664,11 @@
|
||||||
2EDA07A527EDE31100D6F09B /* Components */ = {
|
2EDA07A527EDE31100D6F09B /* Components */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
2EB1C5E727D77F6100BC43D7 /* TextFieldStore.swift */,
|
||||||
2E35F99127B28E7600EB79CD /* SingleLineTextField.swift */,
|
2E35F99127B28E7600EB79CD /* SingleLineTextField.swift */,
|
||||||
2EDA079F27EDE18C00D6F09B /* TextFieldInput.swift */,
|
2EDA079F27EDE18C00D6F09B /* TextFieldInput.swift */,
|
||||||
2EDA07A127EDE1AE00D6F09B /* TextFieldFooter.swift */,
|
2EDA07A127EDE1AE00D6F09B /* TextFieldFooter.swift */,
|
||||||
2E35F99927B3E99C00EB79CD /* TextFieldTitleAccessoryButtonStyle.swift */,
|
2E35F99927B3E99C00EB79CD /* TextFieldTitleAccessoryButtonStyle.swift */,
|
||||||
2EB1C5E727D77F6100BC43D7 /* TextFieldStore.swift */,
|
|
||||||
);
|
);
|
||||||
path = Components;
|
path = Components;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -806,11 +817,34 @@
|
||||||
9E5BF642281FEC8700BA3F17 /* SendTests */ = {
|
9E5BF642281FEC8700BA3F17 /* SendTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
9EDDEA9F2829610D00B4100C /* CurrencySelectionTests.swift */,
|
||||||
9E5BF643281FEC9900BA3F17 /* SendTests.swift */,
|
9E5BF643281FEC9900BA3F17 /* SendTests.swift */,
|
||||||
|
9EDDEAA12829610D00B4100C /* TransactionAddressInputTests.swift */,
|
||||||
|
9EDDEAA02829610D00B4100C /* TransactionAmountInputTests.swift */,
|
||||||
);
|
);
|
||||||
path = SendTests;
|
path = SendTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
9E5BF64B2823C91200BA3F17 /* TransactionAmount */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
2EB7758627FC67FD00269373 /* TransactionAmountInputStore.swift */,
|
||||||
|
2E8719CA27FB09990082C926 /* TransactionAmountTextField.swift */,
|
||||||
|
2E6CF8DC27D78319004DCD7A /* CurrencySelectionStore.swift */,
|
||||||
|
2E8719CC27FB0D3B0082C926 /* TransactionCurrencySelector.swift */,
|
||||||
|
);
|
||||||
|
path = TransactionAmount;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9E5BF64C2823E84300BA3F17 /* TransactionAddress */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9E5BF64E2823E94900BA3F17 /* TransactionAddressInputStore.swift */,
|
||||||
|
9E5BF64D2823E94900BA3F17 /* TransactionAddressTextField.swift */,
|
||||||
|
);
|
||||||
|
path = TransactionAddress;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
9EAFEB802805791400199FC9 /* AppReducerTests */ = {
|
9EAFEB802805791400199FC9 /* AppReducerTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -1224,7 +1258,7 @@
|
||||||
0D35CC46277A36E00074316A /* ScrollableWhenScaled.swift in Sources */,
|
0D35CC46277A36E00074316A /* ScrollableWhenScaled.swift in Sources */,
|
||||||
F96B41E9273B501F0021B49A /* TransactionHistoryView.swift in Sources */,
|
F96B41E9273B501F0021B49A /* TransactionHistoryView.swift in Sources */,
|
||||||
2EDA07A027EDE18C00D6F09B /* TextFieldInput.swift in Sources */,
|
2EDA07A027EDE18C00D6F09B /* TextFieldInput.swift in Sources */,
|
||||||
2EB7758727FC67FD00269373 /* TransactionInputStore.swift in Sources */,
|
2EB7758727FC67FD00269373 /* TransactionAmountInputStore.swift in Sources */,
|
||||||
669FDAE9272C23B3007B9422 /* CircularFrame.swift in Sources */,
|
669FDAE9272C23B3007B9422 /* CircularFrame.swift in Sources */,
|
||||||
9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */,
|
9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */,
|
||||||
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */,
|
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */,
|
||||||
|
@ -1254,6 +1288,7 @@
|
||||||
669FDAEB272C23C2007B9422 /* CircularFrameBadge.swift in Sources */,
|
669FDAEB272C23C2007B9422 /* CircularFrameBadge.swift in Sources */,
|
||||||
2E8719CD27FB0D3B0082C926 /* TransactionCurrencySelector.swift in Sources */,
|
2E8719CD27FB0D3B0082C926 /* TransactionCurrencySelector.swift in Sources */,
|
||||||
F9971A6C27680E1000A2DB75 /* WalletInfoView.swift in Sources */,
|
F9971A6C27680E1000A2DB75 /* WalletInfoView.swift in Sources */,
|
||||||
|
9E5BF6502823E94900BA3F17 /* TransactionAddressInputStore.swift in Sources */,
|
||||||
F9EEB8162742C2210032EEB8 /* WithStateBinding.swift in Sources */,
|
F9EEB8162742C2210032EEB8 /* WithStateBinding.swift in Sources */,
|
||||||
F93673D62742CB840099C6AF /* Previews.swift in Sources */,
|
F93673D62742CB840099C6AF /* Previews.swift in Sources */,
|
||||||
0D5D16F526E24CCF00AD33D1 /* AppError.swift in Sources */,
|
0D5D16F526E24CCF00AD33D1 /* AppError.swift in Sources */,
|
||||||
|
@ -1294,12 +1329,14 @@
|
||||||
0DF2DC5427235E3E00FA31E2 /* View+InnerShadow.swift in Sources */,
|
0DF2DC5427235E3E00FA31E2 /* View+InnerShadow.swift in Sources */,
|
||||||
9EAFEB84280597B700199FC9 /* WrappedSecItem.swift in Sources */,
|
9EAFEB84280597B700199FC9 /* WrappedSecItem.swift in Sources */,
|
||||||
9E2AC10327DA28200042AA47 /* WalletStorage.swift in Sources */,
|
9E2AC10327DA28200042AA47 /* WalletStorage.swift in Sources */,
|
||||||
|
9EDDEA8C28250F9C00B4100C /* Double+Zcash.swift in Sources */,
|
||||||
9ECAE56827FC713C0089A0EF /* DatabaseFiles.swift in Sources */,
|
9ECAE56827FC713C0089A0EF /* DatabaseFiles.swift in Sources */,
|
||||||
9E5BF6462821028C00BA3F17 /* WrappedUserDefaults.swift in Sources */,
|
9E5BF6462821028C00BA3F17 /* WrappedUserDefaults.swift in Sources */,
|
||||||
F9971A6B27680E1000A2DB75 /* WalletInfo.swift in Sources */,
|
F9971A6B27680E1000A2DB75 /* WalletInfo.swift in Sources */,
|
||||||
0D185819272723FF0046B928 /* ColoredChip.swift in Sources */,
|
0D185819272723FF0046B928 /* ColoredChip.swift in Sources */,
|
||||||
2EA11F5D27467F7700709571 /* OnboardingContentView.swift in Sources */,
|
2EA11F5D27467F7700709571 /* OnboardingContentView.swift in Sources */,
|
||||||
2E58E73B274679F000B2B84B /* OnboardingHeaderView.swift in Sources */,
|
2E58E73B274679F000B2B84B /* OnboardingHeaderView.swift in Sources */,
|
||||||
|
9E5BF64F2823E94900BA3F17 /* TransactionAddressTextField.swift in Sources */,
|
||||||
2E35F99227B28E7600EB79CD /* SingleLineTextField.swift in Sources */,
|
2E35F99227B28E7600EB79CD /* SingleLineTextField.swift in Sources */,
|
||||||
0D8A43C6272B129C005A6414 /* WordChipGrid.swift in Sources */,
|
0D8A43C6272B129C005A6414 /* WordChipGrid.swift in Sources */,
|
||||||
66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */,
|
66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */,
|
||||||
|
@ -1322,7 +1359,7 @@
|
||||||
F9971A6527680DFE00A2DB75 /* Settings.swift in Sources */,
|
F9971A6527680DFE00A2DB75 /* Settings.swift in Sources */,
|
||||||
9EF8139C27F47AED0075AF48 /* InitializationState.swift in Sources */,
|
9EF8139C27F47AED0075AF48 /* InitializationState.swift in Sources */,
|
||||||
0D0781C9278776D20083ACD7 /* ZcashSymbol.swift in Sources */,
|
0D0781C9278776D20083ACD7 /* ZcashSymbol.swift in Sources */,
|
||||||
2E8719CB27FB09990082C926 /* TransactionTextField.swift in Sources */,
|
2E8719CB27FB09990082C926 /* TransactionAmountTextField.swift in Sources */,
|
||||||
6654C7412715A47300901167 /* Onboarding.swift in Sources */,
|
6654C7412715A47300901167 /* Onboarding.swift in Sources */,
|
||||||
F9C165C42740403600592F76 /* TransactionSentView.swift in Sources */,
|
F9C165C42740403600592F76 /* TransactionSentView.swift in Sources */,
|
||||||
F9971A5927680DDE00A2DB75 /* RequestStore.swift in Sources */,
|
F9971A5927680DDE00A2DB75 /* RequestStore.swift in Sources */,
|
||||||
|
@ -1334,7 +1371,10 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
0DFE93DF272C6D4B000FCCA5 /* RecoveryPhraseBackupTests.swift in Sources */,
|
0DFE93DF272C6D4B000FCCA5 /* RecoveryPhraseBackupTests.swift in Sources */,
|
||||||
|
9EDDEAA22829610D00B4100C /* CurrencySelectionTests.swift in Sources */,
|
||||||
|
9EDDEAA42829610D00B4100C /* TransactionAddressInputTests.swift in Sources */,
|
||||||
6654C7442715A4AC00901167 /* OnboardingStoreTests.swift in Sources */,
|
6654C7442715A4AC00901167 /* OnboardingStoreTests.swift in Sources */,
|
||||||
|
9EDDEAA32829610D00B4100C /* TransactionAmountInputTests.swift in Sources */,
|
||||||
9EAFEB862805A23100199FC9 /* WrappedSecItemTests.swift in Sources */,
|
9EAFEB862805A23100199FC9 /* WrappedSecItemTests.swift in Sources */,
|
||||||
9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */,
|
9E5BF644281FEC9900BA3F17 /* SendTests.swift in Sources */,
|
||||||
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
|
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */,
|
||||||
|
|
|
@ -1,184 +1,187 @@
|
||||||
{
|
{
|
||||||
"pins" : [
|
"object": {
|
||||||
{
|
"pins": [
|
||||||
"identity" : "combine-schedulers",
|
{
|
||||||
"kind" : "remoteSourceControl",
|
"package": "combine-schedulers",
|
||||||
"location" : "https://github.com/pointfreeco/combine-schedulers",
|
"repositoryURL": "https://github.com/pointfreeco/combine-schedulers",
|
||||||
"state" : {
|
"state": {
|
||||||
"revision" : "4cf088c29a20f52be0f2ca54992b492c54e0076b",
|
"branch": null,
|
||||||
"version" : "0.5.3"
|
"revision": "4cf088c29a20f52be0f2ca54992b492c54e0076b",
|
||||||
|
"version": "0.5.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "grpc-swift",
|
||||||
|
"repositoryURL": "https://github.com/grpc/grpc-swift.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "593fe0fe931f7e838969243cd137be48e8055b1d",
|
||||||
|
"version": "1.7.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "MnemonicSwift",
|
||||||
|
"repositoryURL": "https://github.com/zcash-hackworks/MnemonicSwift",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "b10b0b8ee1f297e33ea5b1bc041ced49943b6582",
|
||||||
|
"version": "2.2.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "SQLite.swift",
|
||||||
|
"repositoryURL": "https://github.com/stephencelis/SQLite.swift.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "4d543d811ee644fa4cc4bfa0be996b4dd6ba0f54",
|
||||||
|
"version": "0.13.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-case-paths",
|
||||||
|
"repositoryURL": "https://github.com/pointfreeco/swift-case-paths",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "d226d167bd4a68b51e352af5655c92bce8ee0463",
|
||||||
|
"version": "0.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-collections",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-collections",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "2d33a0ea89c961dcb2b3da2157963d9c0370347e",
|
||||||
|
"version": "1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-composable-architecture",
|
||||||
|
"repositoryURL": "https://github.com/pointfreeco/swift-composable-architecture",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "599a2398adaaa7a4e3f5420cde7728c39e33677e",
|
||||||
|
"version": "0.28.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-crypto",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-crypto.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "067254c79435de759aeef4a6a03e43d087d61312",
|
||||||
|
"version": "2.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-custom-dump",
|
||||||
|
"repositoryURL": "https://github.com/pointfreeco/swift-custom-dump",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "21f8fdbb3226e5e28a1a2fffac3e0f3deec34bf0",
|
||||||
|
"version": "0.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-identified-collections",
|
||||||
|
"repositoryURL": "https://github.com/pointfreeco/swift-identified-collections",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "f76e7d3fe4265ee09216044ec3780d74f546ca82",
|
||||||
|
"version": "0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-log",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-log.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
|
||||||
|
"version": "1.4.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "d6e3762e0a5f7ede652559f53623baf11006e17c",
|
||||||
|
"version": "2.39.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio-extras",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio-extras.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "f73ca5ee9c6806800243f1ac415fcf82de9a4c91",
|
||||||
|
"version": "1.10.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio-http2",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio-http2.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "50c25c132b140e62b45e90b5a76f13ded02c8a46",
|
||||||
|
"version": "1.20.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio-ssl",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "b5260a31c2a72a89fa684f5efb3054d8725a2316",
|
||||||
|
"version": "2.18.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "swift-nio-transport-services",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-nio-transport-services.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "8ab824b140d0ebcd87e9149266ddc353e3705a3e",
|
||||||
|
"version": "1.11.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "SwiftProtobuf",
|
||||||
|
"repositoryURL": "https://github.com/apple/swift-protobuf.git",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "e1499bc69b9040b29184f7f2996f7bab467c1639",
|
||||||
|
"version": "1.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "xctest-dynamic-overlay",
|
||||||
|
"repositoryURL": "https://github.com/pointfreeco/xctest-dynamic-overlay",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "50a70a9d3583fe228ce672e8923010c8df2deddd",
|
||||||
|
"version": "0.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "libzcashlc",
|
||||||
|
"repositoryURL": "https://github.com/zcash-hackworks/zcash-light-client-ffi.git",
|
||||||
|
"state": {
|
||||||
|
"branch": "main",
|
||||||
|
"revision": "8d4cff1ac9afccd7d7b6c4317dfe5e30c5c5bb42",
|
||||||
|
"version": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"package": "ZcashLightClientKit",
|
||||||
|
"repositoryURL": "https://github.com/zcash/ZcashLightClientKit",
|
||||||
|
"state": {
|
||||||
|
"branch": null,
|
||||||
|
"revision": "f3150072f5cafd53fa064b7cfc80ef3a84460fb2",
|
||||||
|
"version": null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
]
|
||||||
{
|
},
|
||||||
"identity" : "grpc-swift",
|
"version": 1
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/grpc/grpc-swift.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "593fe0fe931f7e838969243cd137be48e8055b1d",
|
|
||||||
"version" : "1.7.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "mnemonicswift",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/zcash-hackworks/MnemonicSwift",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "716a2c32ac2bbd8a1499ac834077df42b75edc85",
|
|
||||||
"version" : "2.2.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "sqlite.swift",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/stephencelis/SQLite.swift.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "4d543d811ee644fa4cc4bfa0be996b4dd6ba0f54",
|
|
||||||
"version" : "0.13.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-case-paths",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/pointfreeco/swift-case-paths",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "ce9c0d897db8a840c39de64caaa9b60119cf4be8",
|
|
||||||
"version" : "0.8.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-collections",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-collections",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "48254824bb4248676bf7ce56014ff57b142b77eb",
|
|
||||||
"version" : "1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-composable-architecture",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/pointfreeco/swift-composable-architecture",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "599a2398adaaa7a4e3f5420cde7728c39e33677e",
|
|
||||||
"version" : "0.28.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-crypto",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-crypto.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "d9825fa541df64b1a7b182178d61b9a82730d01f",
|
|
||||||
"version" : "2.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-custom-dump",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/pointfreeco/swift-custom-dump",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "c4f78db9b90ca57b7b6abc2223e235242739ea3c",
|
|
||||||
"version" : "0.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-identified-collections",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/pointfreeco/swift-identified-collections",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "680bf440178a78a627b1c2c64c0855f6523ad5b9",
|
|
||||||
"version" : "0.3.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-log",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-log.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "5d66f7ba25daf4f94100e7022febf3c75e37a6c7",
|
|
||||||
"version" : "1.4.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-nio",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-nio.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "124119f0bb12384cef35aa041d7c3a686108722d",
|
|
||||||
"version" : "2.40.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-nio-extras",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-nio-extras.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "8eea84ec6144167354387ef9244b0939f5852dc8",
|
|
||||||
"version" : "1.11.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-nio-http2",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-nio-http2.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "72bcaf607b40d7c51044f65b0f5ed8581a911832",
|
|
||||||
"version" : "1.21.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-nio-ssl",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-nio-ssl.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "1750873bce84b4129b5303655cce2c3d35b9ed3a",
|
|
||||||
"version" : "2.19.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-nio-transport-services",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-nio-transport-services.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "1a4692acb88156e3da1b0c6732a8a38b2a744166",
|
|
||||||
"version" : "1.12.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "swift-protobuf",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/apple/swift-protobuf.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "e1499bc69b9040b29184f7f2996f7bab467c1639",
|
|
||||||
"version" : "1.19.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "xctest-dynamic-overlay",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/pointfreeco/xctest-dynamic-overlay",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "50a70a9d3583fe228ce672e8923010c8df2deddd",
|
|
||||||
"version" : "0.2.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "zcash-light-client-ffi",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi.git",
|
|
||||||
"state" : {
|
|
||||||
"branch" : "main",
|
|
||||||
"revision" : "1d236d07b9f8ea7d1380175cdef5c00bde70eed8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"identity" : "zcashlightclientkit",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/zcash/ZcashLightClientKit",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "f3150072f5cafd53fa064b7cfc80ef3a84460fb2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"version" : 2
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,9 @@ struct HomeState: Equatable {
|
||||||
var sendState: SendState
|
var sendState: SendState
|
||||||
var scanState: ScanState
|
var scanState: ScanState
|
||||||
var synchronizerStatus: String
|
var synchronizerStatus: String
|
||||||
var totalBalance: Double
|
var totalBalance: Int64
|
||||||
var transactionHistoryState: TransactionHistoryState
|
var transactionHistoryState: TransactionHistoryState
|
||||||
var verifiedBalance: Double
|
var verifiedBalance: Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
enum HomeAction: Equatable {
|
enum HomeAction: Equatable {
|
||||||
|
@ -95,8 +95,8 @@ extension HomeReducer {
|
||||||
return Effect(value: .updateSynchronizerStatus)
|
return Effect(value: .updateSynchronizerStatus)
|
||||||
|
|
||||||
case .updateBalance(let balance):
|
case .updateBalance(let balance):
|
||||||
state.totalBalance = balance.total.asHumanReadableZecBalance()
|
state.totalBalance = balance.total
|
||||||
state.verifiedBalance = balance.verified.asHumanReadableZecBalance()
|
state.verifiedBalance = balance.verified
|
||||||
return .none
|
return .none
|
||||||
|
|
||||||
case .updateDrawer(let drawerOverlay):
|
case .updateDrawer(let drawerOverlay):
|
||||||
|
@ -244,9 +244,9 @@ extension HomeState {
|
||||||
sendState: .placeholder,
|
sendState: .placeholder,
|
||||||
scanState: .placeholder,
|
scanState: .placeholder,
|
||||||
synchronizerStatus: "",
|
synchronizerStatus: "",
|
||||||
totalBalance: 0.0,
|
totalBalance: 0,
|
||||||
transactionHistoryState: .emptyPlaceHolder,
|
transactionHistoryState: .emptyPlaceHolder,
|
||||||
verifiedBalance: 0.0
|
verifiedBalance: 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ struct HomeView: View {
|
||||||
Text("\(viewStore.synchronizerStatus)")
|
Text("\(viewStore.synchronizerStatus)")
|
||||||
.padding(.top, 60)
|
.padding(.top, 60)
|
||||||
|
|
||||||
Text("balance: \(viewStore.totalBalance)")
|
Text("balance \(viewStore.totalBalance.asZecString()) ZEC")
|
||||||
.accessDebugMenuWithHiddenGesture {
|
.accessDebugMenuWithHiddenGesture {
|
||||||
viewStore.send(.debugMenuStartup)
|
viewStore.send(.debugMenuStartup)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ struct ImportSeedEditor: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
WithViewStore(store) { viewStore in
|
WithViewStore(store) { viewStore in
|
||||||
TextEditor(text: viewStore.binding(\.$importedSeedPhrase))
|
TextEditor(text: viewStore.binding(\.$importedSeedPhrase))
|
||||||
|
.autocapitalization(.none)
|
||||||
.importSeedEditorModifier()
|
.importSeedEditorModifier()
|
||||||
.padding(28)
|
.padding(28)
|
||||||
}
|
}
|
||||||
|
@ -27,11 +28,13 @@ struct ImportSeedEditor: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ImportSeedEditorModifier: ViewModifier {
|
struct ImportSeedEditorModifier: ViewModifier {
|
||||||
|
var backgroundColor = Color.white
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
content
|
content
|
||||||
.foregroundColor(Asset.Colors.Text.importSeedEditor.color)
|
.foregroundColor(Asset.Colors.Text.importSeedEditor.color)
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color.white)
|
.background(backgroundColor)
|
||||||
.cornerRadius(4)
|
.cornerRadius(4)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 4)
|
RoundedRectangle(cornerRadius: 4)
|
||||||
|
@ -41,8 +44,8 @@ struct ImportSeedEditorModifier: ViewModifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
func importSeedEditorModifier() -> some View {
|
func importSeedEditorModifier(_ backgroundColor: Color = .white) -> some View {
|
||||||
modifier(ImportSeedEditorModifier())
|
modifier(ImportSeedEditorModifier(backgroundColor: backgroundColor))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,6 @@ struct Transaction: Equatable {
|
||||||
var amount: Int64
|
var amount: Int64
|
||||||
var memo: String
|
var memo: String
|
||||||
var toAddress: String
|
var toAddress: String
|
||||||
|
|
||||||
var amountString: String {
|
|
||||||
get { amount == 0 ? "" : String(format: "%.7f", amount.asHumanReadableZecBalance()) }
|
|
||||||
set { amount = Int64((newValue as NSString).doubleValue * 100_000_000) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Transaction {
|
extension Transaction {
|
||||||
|
@ -25,8 +20,7 @@ extension Transaction {
|
||||||
|
|
||||||
struct SendState: Equatable {
|
struct SendState: Equatable {
|
||||||
enum Route: Equatable {
|
enum Route: Equatable {
|
||||||
case showConfirmation
|
case confirmation
|
||||||
case showSent
|
|
||||||
case success
|
case success
|
||||||
case failure
|
case failure
|
||||||
case done
|
case done
|
||||||
|
@ -35,9 +29,35 @@ struct SendState: Equatable {
|
||||||
var route: Route?
|
var route: Route?
|
||||||
|
|
||||||
var isSendingTransaction = false
|
var isSendingTransaction = false
|
||||||
var totalBalance = 0.0
|
var memo = ""
|
||||||
|
var totalBalance: Int64 = 0
|
||||||
var transaction: Transaction
|
var transaction: Transaction
|
||||||
var transactionInputState: TransactionInputState
|
var transactionAddressInputState: TransactionAddressInputState
|
||||||
|
var transactionAmountInputState: TransactionAmountInputState
|
||||||
|
|
||||||
|
var isInvalidAddressFormat: Bool {
|
||||||
|
!transactionAddressInputState.isValidAddress
|
||||||
|
&& !transactionAddressInputState.textFieldState.text.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
var isInvalidAmountFormat: Bool {
|
||||||
|
!transactionAmountInputState.textFieldState.valid
|
||||||
|
&& !transactionAmountInputState.textFieldState.text.isEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
var isValidForm: Bool {
|
||||||
|
transactionAmountInputState.amount > 0
|
||||||
|
&& transactionAddressInputState.isValidAddress
|
||||||
|
&& !isInsufficientFunds
|
||||||
|
}
|
||||||
|
|
||||||
|
var isInsufficientFunds: Bool {
|
||||||
|
transactionAmountInputState.amount > transactionAmountInputState.maxValue
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalCurrencyBalance: Int64 {
|
||||||
|
(totalBalance.asHumanReadableZecBalance() * transactionAmountInputState.zecPrice).asZec()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SendAction: Equatable {
|
enum SendAction: Equatable {
|
||||||
|
@ -46,8 +66,10 @@ enum SendAction: Equatable {
|
||||||
case sendConfirmationPressed
|
case sendConfirmationPressed
|
||||||
case sendTransactionResult(Result<TransactionState, NSError>)
|
case sendTransactionResult(Result<TransactionState, NSError>)
|
||||||
case synchronizerStateChanged(WrappedSDKSynchronizerState)
|
case synchronizerStateChanged(WrappedSDKSynchronizerState)
|
||||||
case transactionInput(TransactionInputAction)
|
case transactionAddressInput(TransactionAddressInputAction)
|
||||||
case updateBalance(Double)
|
case transactionAmountInput(TransactionAmountInputAction)
|
||||||
|
case updateBalance(Int64)
|
||||||
|
case updateMemo(String)
|
||||||
case updateTransaction(Transaction)
|
case updateTransaction(Transaction)
|
||||||
case updateRoute(SendState.Route?)
|
case updateRoute(SendState.Route?)
|
||||||
}
|
}
|
||||||
|
@ -71,9 +93,9 @@ extension SendReducer {
|
||||||
|
|
||||||
static let `default` = SendReducer.combine(
|
static let `default` = SendReducer.combine(
|
||||||
[
|
[
|
||||||
balanceReducer,
|
|
||||||
sendReducer,
|
sendReducer,
|
||||||
transactionInputReducer
|
transactionAddressInputReducer,
|
||||||
|
transactionAmountInputReducer
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
.debug()
|
.debug()
|
||||||
|
@ -89,6 +111,11 @@ extension SendReducer {
|
||||||
state.isSendingTransaction = false
|
state.isSendingTransaction = false
|
||||||
return .none
|
return .none
|
||||||
|
|
||||||
|
case .updateRoute(.confirmation):
|
||||||
|
state.transaction.amount = state.transactionAmountInputState.amount
|
||||||
|
state.transaction.toAddress = state.transactionAddressInputState.textFieldState.text
|
||||||
|
return .none
|
||||||
|
|
||||||
case let .updateRoute(route):
|
case let .updateRoute(route):
|
||||||
state.route = route
|
state.route = route
|
||||||
return .none
|
return .none
|
||||||
|
@ -129,16 +156,13 @@ extension SendReducer {
|
||||||
} catch {
|
} catch {
|
||||||
return Effect(value: .updateRoute(.failure))
|
return Effect(value: .updateRoute(.failure))
|
||||||
}
|
}
|
||||||
case .transactionInput(let transactionInput):
|
|
||||||
|
case .transactionAmountInput(let transactionInput):
|
||||||
return .none
|
return .none
|
||||||
|
|
||||||
default:
|
case .transactionAddressInput(let transactionInput):
|
||||||
return .none
|
return .none
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static let balanceReducer = SendReducer { state, action, environment in
|
|
||||||
switch action {
|
|
||||||
case .onAppear:
|
case .onAppear:
|
||||||
return environment.wrappedSDKSynchronizer.stateChanged
|
return environment.wrappedSDKSynchronizer.stateChanged
|
||||||
.map(SendAction.synchronizerStateChanged)
|
.map(SendAction.synchronizerStateChanged)
|
||||||
|
@ -151,7 +175,7 @@ extension SendReducer {
|
||||||
case .synchronizerStateChanged(.synced):
|
case .synchronizerStateChanged(.synced):
|
||||||
return environment.wrappedSDKSynchronizer.getShieldedBalance()
|
return environment.wrappedSDKSynchronizer.getShieldedBalance()
|
||||||
.receive(on: environment.scheduler)
|
.receive(on: environment.scheduler)
|
||||||
.map({ Double($0.total) / Double(100_000_000) })
|
.map({ $0.total })
|
||||||
.map(SendAction.updateBalance)
|
.map(SendAction.updateBalance)
|
||||||
.eraseToEffect()
|
.eraseToEffect()
|
||||||
|
|
||||||
|
@ -160,18 +184,29 @@ extension SendReducer {
|
||||||
|
|
||||||
case .updateBalance(let balance):
|
case .updateBalance(let balance):
|
||||||
state.totalBalance = balance
|
state.totalBalance = balance
|
||||||
state.transactionInputState.maxValue = Int64(balance * 100_000_000)
|
state.transactionAmountInputState.maxValue = balance
|
||||||
return .none
|
return .none
|
||||||
|
|
||||||
default:
|
case .updateMemo(let memo):
|
||||||
|
state.memo = memo
|
||||||
return .none
|
return .none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static let transactionInputReducer: SendReducer = TransactionInputReducer.default.pullback(
|
private static let transactionAddressInputReducer: SendReducer = TransactionAddressInputReducer.default.pullback(
|
||||||
state: \SendState.transactionInputState,
|
state: \SendState.transactionAddressInputState,
|
||||||
action: /SendAction.transactionInput,
|
action: /SendAction.transactionAddressInput,
|
||||||
environment: { _ in TransactionInputEnvironment() }
|
environment: { environment in
|
||||||
|
TransactionAddressInputEnvironment(
|
||||||
|
wrappedDerivationTool: environment.wrappedDerivationTool
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
private static let transactionAmountInputReducer: SendReducer = TransactionAmountInputReducer.default.pullback(
|
||||||
|
state: \SendState.transactionAmountInputState,
|
||||||
|
action: /SendAction.transactionAmountInput,
|
||||||
|
environment: { _ in TransactionAmountInputEnvironment() }
|
||||||
)
|
)
|
||||||
|
|
||||||
static func `default`(whenDone: @escaping () -> Void) -> SendReducer {
|
static func `default`(whenDone: @escaping () -> Void) -> SendReducer {
|
||||||
|
@ -211,36 +246,36 @@ extension SendViewStore {
|
||||||
|
|
||||||
var bindingForConfirmation: Binding<Bool> {
|
var bindingForConfirmation: Binding<Bool> {
|
||||||
self.routeBinding.map(
|
self.routeBinding.map(
|
||||||
extract: { $0 == .showConfirmation || self.bindingForSuccess.wrappedValue || self.bindingForFailure.wrappedValue },
|
extract: { $0 == .confirmation || self.bindingForSuccess.wrappedValue || self.bindingForFailure.wrappedValue },
|
||||||
embed: { $0 ? SendState.Route.showConfirmation : nil }
|
embed: { $0 ? SendState.Route.confirmation : nil }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var bindingForSuccess: Binding<Bool> {
|
var bindingForSuccess: Binding<Bool> {
|
||||||
self.routeBinding.map(
|
self.routeBinding.map(
|
||||||
extract: { $0 == .success || self.bindingForDone.wrappedValue },
|
extract: { $0 == .success || self.bindingForDone.wrappedValue },
|
||||||
embed: { $0 ? SendState.Route.success : SendState.Route.showConfirmation }
|
embed: { $0 ? SendState.Route.success : SendState.Route.confirmation }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var bindingForFailure: Binding<Bool> {
|
var bindingForFailure: Binding<Bool> {
|
||||||
self.routeBinding.map(
|
self.routeBinding.map(
|
||||||
extract: { $0 == .failure || self.bindingForDone.wrappedValue },
|
extract: { $0 == .failure || self.bindingForDone.wrappedValue },
|
||||||
embed: { $0 ? SendState.Route.failure : SendState.Route.showConfirmation }
|
embed: { $0 ? SendState.Route.failure : SendState.Route.confirmation }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var bindingForDone: Binding<Bool> {
|
var bindingForDone: Binding<Bool> {
|
||||||
self.routeBinding.map(
|
self.routeBinding.map(
|
||||||
extract: { $0 == .done },
|
extract: { $0 == .done },
|
||||||
embed: { $0 ? SendState.Route.done : SendState.Route.showConfirmation }
|
embed: { $0 ? SendState.Route.done : SendState.Route.confirmation }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
var bindingForBalance: Binding<Double> {
|
var bindingForMemo: Binding<String> {
|
||||||
self.binding(
|
self.binding(
|
||||||
get: \.totalBalance,
|
get: \.memo,
|
||||||
send: SendAction.updateBalance
|
send: SendAction.updateMemo
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,7 +287,8 @@ extension SendState {
|
||||||
.init(
|
.init(
|
||||||
route: nil,
|
route: nil,
|
||||||
transaction: .placeholder,
|
transaction: .placeholder,
|
||||||
transactionInputState: .placeholer
|
transactionAddressInputState: .placeholder,
|
||||||
|
transactionAmountInputState: .amount
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +300,8 @@ extension SendState {
|
||||||
memo: "",
|
memo: "",
|
||||||
toAddress: ""
|
toAddress: ""
|
||||||
),
|
),
|
||||||
transactionInputState: .placeholer
|
transactionAddressInputState: .placeholder,
|
||||||
|
transactionAmountInputState: .placeholder
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,73 +2,89 @@ import SwiftUI
|
||||||
import ComposableArchitecture
|
import ComposableArchitecture
|
||||||
|
|
||||||
struct CreateTransaction: View {
|
struct CreateTransaction: View {
|
||||||
let store: TransactionInputStore
|
let store: SendStore
|
||||||
|
|
||||||
@Binding var transaction: Transaction
|
|
||||||
@Binding var isComplete: Bool
|
|
||||||
@Binding var totalBalance: Double
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
UITextView.appearance().backgroundColor = .clear
|
UITextView.appearance().backgroundColor = .clear
|
||||||
|
|
||||||
return WithViewStore(store) { viewStore in
|
return WithViewStore(store) { viewStore in
|
||||||
VStack {
|
VStack {
|
||||||
VStack {
|
VStack(spacing: 0) {
|
||||||
Text("Balance \(totalBalance)")
|
Text("Balance \(viewStore.totalBalance.asZecString()) ZEC")
|
||||||
|
Text("($\(viewStore.totalCurrencyBalance.asZecString()))")
|
||||||
SingleLineTextField(
|
.font(.system(size: 13))
|
||||||
placeholderText: "0",
|
.opacity(0.6)
|
||||||
title: "How much ZEC would you like to send?",
|
|
||||||
store: store.scope(
|
|
||||||
state: \.textFieldState,
|
|
||||||
action: TransactionInputAction.textField
|
|
||||||
),
|
|
||||||
titleAccessoryView: {
|
|
||||||
Button(
|
|
||||||
action: { viewStore.send(.setMax(viewStore.maxValue)) },
|
|
||||||
label: { Text("Max") }
|
|
||||||
)
|
|
||||||
.textFieldTitleAccessoryButtonStyle
|
|
||||||
},
|
|
||||||
inputAccessoryView: {
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
Text("To Address")
|
TransactionAmountTextField(
|
||||||
|
store: store.scope(
|
||||||
TextField(
|
state: \.transactionAmountInputState,
|
||||||
"Address",
|
action: SendAction.transactionAmountInput
|
||||||
text: $transaction.toAddress
|
)
|
||||||
)
|
)
|
||||||
.font(.system(size: 14))
|
|
||||||
.padding()
|
if viewStore.isInvalidAmountFormat {
|
||||||
.background(Color.white)
|
HStack {
|
||||||
.foregroundColor(Asset.Colors.Text.importSeedEditor.color)
|
Text("invalid amount")
|
||||||
|
.foregroundColor(.red)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if viewStore.isInsufficientFunds {
|
||||||
|
HStack {
|
||||||
|
Text("insufficient funds")
|
||||||
|
.foregroundColor(.red)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
TransactionAddressTextField(
|
||||||
|
store: store.scope(
|
||||||
|
state: \.transactionAddressInputState,
|
||||||
|
action: SendAction.transactionAddressInput
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if viewStore.isInvalidAddressFormat {
|
||||||
|
HStack {
|
||||||
|
Text("invalid address")
|
||||||
|
.foregroundColor(.red)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
|
||||||
VStack {
|
VStack {
|
||||||
Text("Memo")
|
Text("Memo")
|
||||||
|
|
||||||
TextEditor(text: $transaction.memo)
|
TextEditor(text: viewStore.bindingForMemo)
|
||||||
.frame(maxWidth: .infinity, maxHeight: 150, alignment: .center)
|
.frame(maxWidth: .infinity, maxHeight: 150, alignment: .center)
|
||||||
.importSeedEditorModifier()
|
.importSeedEditorModifier(Asset.Colors.Text.activeButtonText.color)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
action: { isComplete = true },
|
action: { viewStore.send(.updateRoute(.confirmation)) },
|
||||||
label: { Text("Send") }
|
label: { Text("Send") }
|
||||||
)
|
)
|
||||||
.activeButtonStyle
|
.activeButtonStyle
|
||||||
.frame(height: 50)
|
.frame(height: 50)
|
||||||
.padding()
|
.padding()
|
||||||
|
.disabled(!viewStore.isValidForm)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.padding()
|
.padding()
|
||||||
.applyScreenBackground()
|
.applyScreenBackground()
|
||||||
}
|
}
|
||||||
|
@ -81,18 +97,9 @@ struct Create_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
StateContainer(
|
StateContainer(
|
||||||
initialState: (
|
initialState: ( false )
|
||||||
Transaction.placeholder,
|
) { _ in
|
||||||
false,
|
CreateTransaction(store: .placeholder)
|
||||||
0.0
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
CreateTransaction(
|
|
||||||
store: .placeholder,
|
|
||||||
transaction: $0.0,
|
|
||||||
isComplete: $0.1,
|
|
||||||
totalBalance: $0.2
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.preferredColorScheme(.dark)
|
.preferredColorScheme(.dark)
|
||||||
|
@ -107,7 +114,8 @@ extension SendStore {
|
||||||
initialState: .init(
|
initialState: .init(
|
||||||
route: nil,
|
route: nil,
|
||||||
transaction: .placeholder,
|
transaction: .placeholder,
|
||||||
transactionInputState: .placeholer
|
transactionAddressInputState: .placeholder,
|
||||||
|
transactionAmountInputState: .placeholder
|
||||||
),
|
),
|
||||||
reducer: .default,
|
reducer: .default,
|
||||||
environment: SendEnvironment(
|
environment: SendEnvironment(
|
||||||
|
|
|
@ -2,33 +2,17 @@ import SwiftUI
|
||||||
import ComposableArchitecture
|
import ComposableArchitecture
|
||||||
|
|
||||||
struct SendView: View {
|
struct SendView: View {
|
||||||
let store: Store<SendState, SendAction>
|
let store: SendStore
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
WithViewStore(store) { viewStore in
|
WithViewStore(store) { viewStore in
|
||||||
CreateTransaction(
|
CreateTransaction(store: store)
|
||||||
store: store.scope(
|
|
||||||
state: \.transactionInputState,
|
|
||||||
action: SendAction.transactionInput
|
|
||||||
),
|
|
||||||
transaction: viewStore.bindingForTransaction,
|
|
||||||
isComplete: viewStore.bindingForConfirmation,
|
|
||||||
totalBalance: viewStore.bindingForBalance
|
|
||||||
)
|
|
||||||
.onAppear { viewStore.send(.onAppear) }
|
.onAppear { viewStore.send(.onAppear) }
|
||||||
.onDisappear { viewStore.send(.onDisappear) }
|
.onDisappear { viewStore.send(.onDisappear) }
|
||||||
.navigationLinkEmpty(
|
.navigationLinkEmpty(
|
||||||
isActive: viewStore.bindingForConfirmation,
|
isActive: viewStore.bindingForConfirmation,
|
||||||
destination: {
|
destination: {
|
||||||
TransactionConfirmation(viewStore: viewStore)
|
TransactionConfirmation(viewStore: viewStore)
|
||||||
.navigationLinkEmpty(
|
|
||||||
isActive: viewStore.bindingForSuccess,
|
|
||||||
destination: { TransactionSent(viewStore: viewStore) }
|
|
||||||
)
|
|
||||||
.navigationLinkEmpty(
|
|
||||||
isActive: viewStore.bindingForFailure,
|
|
||||||
destination: { TransactionFailed(viewStore: viewStore) }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -43,7 +27,8 @@ struct SendView_Previews: PreviewProvider {
|
||||||
initialState: .init(
|
initialState: .init(
|
||||||
route: nil,
|
route: nil,
|
||||||
transaction: .placeholder,
|
transaction: .placeholder,
|
||||||
transactionInputState: .placeholer
|
transactionAddressInputState: .placeholder,
|
||||||
|
transactionAmountInputState: .placeholder
|
||||||
),
|
),
|
||||||
reducer: .default,
|
reducer: .default,
|
||||||
environment: SendEnvironment(
|
environment: SendEnvironment(
|
||||||
|
|
|
@ -6,7 +6,7 @@ struct TransactionConfirmation: View {
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
Text("Send \(String(format: "%.7f", Int64(viewStore.transactionInputState.amount).asHumanReadableZecBalance())) ZEC")
|
Text("Send \(viewStore.transaction.amount.asZecString()) ZEC")
|
||||||
.padding()
|
.padding()
|
||||||
|
|
||||||
Text("To \(viewStore.transaction.toAddress)")
|
Text("To \(viewStore.transaction.toAddress)")
|
||||||
|
@ -25,6 +25,14 @@ struct TransactionConfirmation: View {
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
.applyScreenBackground()
|
.applyScreenBackground()
|
||||||
|
.navigationLinkEmpty(
|
||||||
|
isActive: viewStore.bindingForSuccess,
|
||||||
|
destination: { TransactionSent(viewStore: viewStore) }
|
||||||
|
)
|
||||||
|
.navigationLinkEmpty(
|
||||||
|
isActive: viewStore.bindingForFailure,
|
||||||
|
destination: { TransactionFailed(viewStore: viewStore) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,7 @@ extension TransactionHistoryView {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Text(transaction.status == .received ? "+" : "")
|
Text(transaction.status == .received ? "+" : "")
|
||||||
+ Text("\(String(format: "%.7f", transaction.zecAmount.asHumanReadableZecBalance())) ZEC")
|
+ Text("\(transaction.zecAmount.asZecString()) ZEC")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationLink(
|
.navigationLink(
|
||||||
|
|
|
@ -63,3 +63,5 @@
|
||||||
"Skip" = "Skip";
|
"Skip" = "Skip";
|
||||||
"Next" = "Next";
|
"Next" = "Next";
|
||||||
"Send" = "Send";
|
"Send" = "Send";
|
||||||
|
"Clear" = "Clear";
|
||||||
|
"Max" = "Max";
|
||||||
|
|
|
@ -8,13 +8,14 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ComposableArchitecture
|
import ComposableArchitecture
|
||||||
|
|
||||||
struct SingleLineTextField<TitleAccessoryContent, InputAccessoryContent>: View
|
struct SingleLineTextField<TitleAccessoryContent, InputPrefixContent, InputAccessoryContent>: View
|
||||||
where TitleAccessoryContent: View, InputAccessoryContent: View {
|
where TitleAccessoryContent: View, InputPrefixContent: View, InputAccessoryContent: View {
|
||||||
let placeholderText: String
|
let placeholderText: String
|
||||||
let title: String
|
let title: String
|
||||||
let store: TextFieldStore
|
let store: TextFieldStore
|
||||||
|
|
||||||
@ViewBuilder let titleAccessoryView: TitleAccessoryContent
|
@ViewBuilder let titleAccessoryView: TitleAccessoryContent
|
||||||
|
@ViewBuilder let inputPrefixView: InputPrefixContent
|
||||||
@ViewBuilder let inputAccessoryView: InputAccessoryContent
|
@ViewBuilder let inputAccessoryView: InputAccessoryContent
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
@ -23,6 +24,7 @@ struct SingleLineTextField<TitleAccessoryContent, InputAccessoryContent>: View
|
||||||
Text(title)
|
Text(title)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.truncationMode(.middle)
|
.truncationMode(.middle)
|
||||||
|
.font(.system(size: 13))
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
@ -30,6 +32,8 @@ struct SingleLineTextField<TitleAccessoryContent, InputAccessoryContent>: View
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
|
inputPrefixView
|
||||||
|
|
||||||
TextFieldInput(
|
TextFieldInput(
|
||||||
placeholder: placeholderText,
|
placeholder: placeholderText,
|
||||||
store: store
|
store: store
|
||||||
|
@ -67,6 +71,7 @@ struct SingleLineTextField_Previews: PreviewProvider {
|
||||||
)
|
)
|
||||||
.textFieldTitleAccessoryButtonStyle
|
.textFieldTitleAccessoryButtonStyle
|
||||||
},
|
},
|
||||||
|
inputPrefixView: { EmptyView() },
|
||||||
inputAccessoryView: { EmptyView() }
|
inputAccessoryView: { EmptyView() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -84,6 +89,7 @@ struct SingleLineTextField_Previews: PreviewProvider {
|
||||||
title: "Who would you like to deal with really long text today?",
|
title: "Who would you like to deal with really long text today?",
|
||||||
store: store,
|
store: store,
|
||||||
titleAccessoryView: { EmptyView() },
|
titleAccessoryView: { EmptyView() },
|
||||||
|
inputPrefixView: { EmptyView() },
|
||||||
inputAccessoryView: { EmptyView() }
|
inputAccessoryView: { EmptyView() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,8 @@ struct TextFieldInput: View {
|
||||||
set: { viewStore.send(.set($0)) }
|
set: { viewStore.send(.set($0)) }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
.autocapitalization(.none)
|
||||||
|
.font(.system(size: 13))
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.truncationMode(.middle)
|
.truncationMode(.middle)
|
||||||
.accentColor(Asset.Colors.Cursor.bar.color)
|
.accentColor(Asset.Colors.Cursor.bar.color)
|
||||||
|
|
|
@ -22,7 +22,6 @@ struct TextFieldState: Equatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum TextFieldAction: Equatable {
|
enum TextFieldAction: Equatable {
|
||||||
// case apply((String) -> String)
|
|
||||||
case set(String)
|
case set(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,9 +30,6 @@ struct TextFieldEnvironment: Equatable { }
|
||||||
extension TextFieldReducer {
|
extension TextFieldReducer {
|
||||||
static let `default` = TextFieldReducer { state, action, _ in
|
static let `default` = TextFieldReducer { state, action, _ in
|
||||||
switch action {
|
switch action {
|
||||||
// case .apply(let action):
|
|
||||||
// state.text = action(state.text)
|
|
||||||
// state.valid = state.text.isValid(for: state.validationType)
|
|
||||||
case .set(let text):
|
case .set(let text):
|
||||||
state.text = text
|
state.text = text
|
||||||
state.valid = state.text.isValid(for: state.validationType)
|
state.valid = state.text.isValid(for: state.validationType)
|
||||||
|
@ -65,4 +61,9 @@ extension TextFieldState {
|
||||||
validationType: nil,
|
validationType: nil,
|
||||||
text: ""
|
text: ""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
static let amount = TextFieldState(
|
||||||
|
validationType: .floatingPoint,
|
||||||
|
text: ""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
//
|
||||||
|
// TransactionAddressInputStore.swift
|
||||||
|
// secant-testnet
|
||||||
|
//
|
||||||
|
// Created by Lukáš Korba on 05/05/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import ComposableArchitecture
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
typealias TransactionAddressInputReducer = Reducer<
|
||||||
|
TransactionAddressInputState,
|
||||||
|
TransactionAddressInputAction,
|
||||||
|
TransactionAddressInputEnvironment
|
||||||
|
>
|
||||||
|
|
||||||
|
typealias TransactionAddressInputStore = Store<TransactionAddressInputState, TransactionAddressInputAction>
|
||||||
|
|
||||||
|
struct TransactionAddressInputState: Equatable {
|
||||||
|
var textFieldState: TextFieldState
|
||||||
|
var isValidAddress = false
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TransactionAddressInputAction: Equatable {
|
||||||
|
case clearAddress
|
||||||
|
case textField(TextFieldAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TransactionAddressInputEnvironment {
|
||||||
|
let wrappedDerivationTool: WrappedDerivationTool
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TransactionAddressInputReducer {
|
||||||
|
static let `default` = TransactionAddressInputReducer.combine(
|
||||||
|
[
|
||||||
|
addressReducer,
|
||||||
|
textFieldReducer
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
private static let addressReducer = TransactionAddressInputReducer { state, action, environment in
|
||||||
|
switch action {
|
||||||
|
case .clearAddress:
|
||||||
|
state.textFieldState.text = ""
|
||||||
|
return .none
|
||||||
|
|
||||||
|
case .textField(.set(let address)):
|
||||||
|
do {
|
||||||
|
state.isValidAddress = try environment.wrappedDerivationTool.isValidZcashAddress(address)
|
||||||
|
} catch {
|
||||||
|
state.isValidAddress = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static let textFieldReducer: TransactionAddressInputReducer = TextFieldReducer.default.pullback(
|
||||||
|
state: \TransactionAddressInputState.textFieldState,
|
||||||
|
action: /TransactionAddressInputAction.textField,
|
||||||
|
environment: { _ in return .init() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TransactionAddressInputState {
|
||||||
|
static let placeholder = TransactionAddressInputState(
|
||||||
|
textFieldState: .placeholder
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TransactionAddressInputStore {
|
||||||
|
static let placeholder = TransactionAddressInputStore(
|
||||||
|
initialState: .placeholder,
|
||||||
|
reducer: .default,
|
||||||
|
environment: TransactionAddressInputEnvironment(
|
||||||
|
wrappedDerivationTool: .live()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
//
|
||||||
|
// TransactionAddressTextField.swift
|
||||||
|
// secant-testnet
|
||||||
|
//
|
||||||
|
// Created by Lukáš Korba on 05/05/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import ComposableArchitecture
|
||||||
|
|
||||||
|
struct TransactionAddressTextField: View {
|
||||||
|
let store: TransactionAddressInputStore
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
WithViewStore(store) { viewStore in
|
||||||
|
VStack {
|
||||||
|
SingleLineTextField(
|
||||||
|
placeholderText: "address",
|
||||||
|
title: "To",
|
||||||
|
store: store.scope(
|
||||||
|
state: \.textFieldState,
|
||||||
|
action: TransactionAddressInputAction.textField
|
||||||
|
),
|
||||||
|
titleAccessoryView: {
|
||||||
|
if !viewStore.textFieldState.text.isEmpty {
|
||||||
|
Button(
|
||||||
|
action: {
|
||||||
|
viewStore.send(.clearAddress)
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
Text("Clear")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.textFieldTitleAccessoryButtonStyle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inputPrefixView: { EmptyView() },
|
||||||
|
inputAccessoryView: {
|
||||||
|
Image(Asset.Assets.Icons.qrCode.name)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 30, height: 30)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TransactionAddressTextField_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
TransactionAddressTextField(
|
||||||
|
store: TransactionAddressInputStore(
|
||||||
|
initialState: .init(
|
||||||
|
textFieldState: .init(
|
||||||
|
validationType: .floatingPoint,
|
||||||
|
text: ""
|
||||||
|
)
|
||||||
|
),
|
||||||
|
reducer: .default,
|
||||||
|
environment: .init(wrappedDerivationTool: .live())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.preferredColorScheme(.dark)
|
||||||
|
.padding(.horizontal, 50)
|
||||||
|
.applyScreenBackground()
|
||||||
|
.previewLayout(.fixed(width: 500, height: 200))
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
import ComposableArchitecture
|
import ComposableArchitecture
|
||||||
|
|
||||||
|
// TODO: Reimplement this into multicurrency supporter, issue #315 (https://github.com/zcash/secant-ios-wallet/issues/315)
|
||||||
|
|
||||||
typealias CurrencySelectionReducer = Reducer<
|
typealias CurrencySelectionReducer = Reducer<
|
||||||
CurrencySelectionState,
|
CurrencySelectionState,
|
||||||
CurrencySelectionAction,
|
CurrencySelectionAction,
|
||||||
|
@ -35,7 +37,7 @@ enum CurrencySelectionAction: Equatable {
|
||||||
case swapCurrencyType
|
case swapCurrencyType
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CurrencySelectionEnvironment: Equatable { }
|
struct CurrencySelectionEnvironment { }
|
||||||
|
|
||||||
extension CurrencySelectionReducer {
|
extension CurrencySelectionReducer {
|
||||||
static var `default`: Self {
|
static var `default`: Self {
|
|
@ -0,0 +1,132 @@
|
||||||
|
//
|
||||||
|
// TransactionAmountInputStore.swift
|
||||||
|
// secant-testnet
|
||||||
|
//
|
||||||
|
// Created by Adam Stener on 4/5/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import ComposableArchitecture
|
||||||
|
|
||||||
|
typealias TransactionAmountInputReducer = Reducer<
|
||||||
|
TransactionAmountInputState,
|
||||||
|
TransactionAmountInputAction,
|
||||||
|
TransactionAmountInputEnvironment
|
||||||
|
>
|
||||||
|
|
||||||
|
typealias TransactionAmountInputStore = Store<TransactionAmountInputState, TransactionAmountInputAction>
|
||||||
|
|
||||||
|
struct TransactionAmountInputState: Equatable {
|
||||||
|
var textFieldState: TextFieldState
|
||||||
|
var currencySelectionState: CurrencySelectionState
|
||||||
|
var maxValue: Int64 = 0
|
||||||
|
// TODO: - Get the ZEC price from the SDK, issue 311, https://github.com/zcash/secant-ios-wallet/issues/311
|
||||||
|
var zecPrice = 140.0
|
||||||
|
|
||||||
|
var amount: Int64 {
|
||||||
|
switch currencySelectionState.currencyType {
|
||||||
|
case .zec:
|
||||||
|
return (textFieldState.text.doubleValue ?? 0.0).asZec()
|
||||||
|
case .usd:
|
||||||
|
return ((textFieldState.text.doubleValue ?? 0.0) / zecPrice).asZec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxCurrencyConvertedValue: Int64 {
|
||||||
|
switch currencySelectionState.currencyType {
|
||||||
|
case .zec:
|
||||||
|
return maxValue
|
||||||
|
case .usd:
|
||||||
|
return (maxValue.asHumanReadableZecBalance() * zecPrice).asZec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isMax: Bool {
|
||||||
|
return amount == maxValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TransactionAmountInputAction: Equatable {
|
||||||
|
case clearValue
|
||||||
|
case setMax
|
||||||
|
case textField(TextFieldAction)
|
||||||
|
case currencySelection(CurrencySelectionAction)
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TransactionAmountInputEnvironment: Equatable {}
|
||||||
|
|
||||||
|
extension TransactionAmountInputReducer {
|
||||||
|
static let `default` = TransactionAmountInputReducer.combine(
|
||||||
|
[
|
||||||
|
textFieldReducer,
|
||||||
|
currencySelectionReducer,
|
||||||
|
maxOverride,
|
||||||
|
currencyUpdate
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
static let maxOverride = TransactionAmountInputReducer { state, action, _ in
|
||||||
|
switch action {
|
||||||
|
case .setMax:
|
||||||
|
state.textFieldState.text = "\(state.maxCurrencyConvertedValue.asZecString())"
|
||||||
|
|
||||||
|
case .clearValue:
|
||||||
|
state.textFieldState.text = ""
|
||||||
|
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
|
||||||
|
static let currencyUpdate = TransactionAmountInputReducer { state, action, _ in
|
||||||
|
switch action {
|
||||||
|
case .currencySelection:
|
||||||
|
guard let currentDoubleValue = state.textFieldState.text.doubleValue else {
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
|
||||||
|
let currencyType = state.currencySelectionState.currencyType
|
||||||
|
|
||||||
|
let newValue = currencyType == .zec ?
|
||||||
|
currentDoubleValue / state.zecPrice :
|
||||||
|
currentDoubleValue * state.zecPrice
|
||||||
|
state.textFieldState.text = "\(newValue.asZecString())"
|
||||||
|
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
|
||||||
|
private static let textFieldReducer: TransactionAmountInputReducer = TextFieldReducer.default.pullback(
|
||||||
|
state: \TransactionAmountInputState.textFieldState,
|
||||||
|
action: /TransactionAmountInputAction.textField,
|
||||||
|
environment: { _ in return .init() }
|
||||||
|
)
|
||||||
|
|
||||||
|
private static let currencySelectionReducer: TransactionAmountInputReducer = CurrencySelectionReducer.default.pullback(
|
||||||
|
state: \TransactionAmountInputState.currencySelectionState,
|
||||||
|
action: /TransactionAmountInputAction.currencySelection,
|
||||||
|
environment: { _ in return .init() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TransactionAmountInputState {
|
||||||
|
static let placeholder = TransactionAmountInputState(
|
||||||
|
textFieldState: .placeholder,
|
||||||
|
currencySelectionState: CurrencySelectionState()
|
||||||
|
)
|
||||||
|
|
||||||
|
static let amount = TransactionAmountInputState(
|
||||||
|
textFieldState: .amount,
|
||||||
|
currencySelectionState: CurrencySelectionState()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TransactionAmountInputStore {
|
||||||
|
static let placeholder = TransactionAmountInputStore(
|
||||||
|
initialState: .placeholder,
|
||||||
|
reducer: .default,
|
||||||
|
environment: TransactionAmountInputEnvironment()
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
//
|
//
|
||||||
// TransactionTextField.swift
|
// TransactionAmountTextField.swift
|
||||||
// secant-testnet
|
// secant-testnet
|
||||||
//
|
//
|
||||||
// Created by Adam Stener on 4/4/22.
|
// Created by Adam Stener on 4/4/22.
|
||||||
|
@ -8,38 +8,43 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import ComposableArchitecture
|
import ComposableArchitecture
|
||||||
|
|
||||||
struct TransactionTextField: View {
|
struct TransactionAmountTextField: View {
|
||||||
let store: TransactionInputStore
|
let store: TransactionAmountInputStore
|
||||||
|
|
||||||
// Constant example used here, this could be injected by a dependency
|
|
||||||
// Access to this value could also be injected into the store as a dependency
|
|
||||||
// with a function to prouce this value.
|
|
||||||
let maxTransactionValue: Int64 = 500
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
WithViewStore(store) { viewStore in
|
WithViewStore(store) { viewStore in
|
||||||
VStack {
|
VStack {
|
||||||
SingleLineTextField(
|
SingleLineTextField(
|
||||||
placeholderText: "$0",
|
placeholderText: "0",
|
||||||
title: "How much?",
|
title: "How much ZEC would you like to send?",
|
||||||
store: store.scope(
|
store: store.scope(
|
||||||
state: \.textFieldState,
|
state: \.textFieldState,
|
||||||
action: TransactionInputAction.textField
|
action: TransactionAmountInputAction.textField
|
||||||
),
|
),
|
||||||
titleAccessoryView: {
|
titleAccessoryView: {
|
||||||
Button(
|
Button(
|
||||||
action: {
|
action: {
|
||||||
viewStore.send(.setMax(maxTransactionValue))
|
viewStore.send(viewStore.isMax ? .clearValue : .setMax)
|
||||||
},
|
},
|
||||||
label: { Text("Max") }
|
label: {
|
||||||
|
Text(viewStore.isMax ? "Clear" : "Max")
|
||||||
|
}
|
||||||
)
|
)
|
||||||
.textFieldTitleAccessoryButtonStyle
|
.textFieldTitleAccessoryButtonStyle
|
||||||
},
|
},
|
||||||
|
inputPrefixView: {
|
||||||
|
if viewStore.currencySelectionState.currencyType == .zec {
|
||||||
|
ZcashSymbol()
|
||||||
|
.frame(width: 12, height: 12, alignment: .center)
|
||||||
|
} else {
|
||||||
|
Text("$")
|
||||||
|
}
|
||||||
|
},
|
||||||
inputAccessoryView: {
|
inputAccessoryView: {
|
||||||
TransactionCurrencySelector(
|
TransactionCurrencySelector(
|
||||||
store: store.scope(
|
store: store.scope(
|
||||||
state: \.currencySelectionState,
|
state: \.currencySelectionState,
|
||||||
action: TransactionInputAction.currencySelection
|
action: TransactionAmountInputAction.currencySelection
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -49,10 +54,10 @@ struct TransactionTextField: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TransactionTextField_Previews: PreviewProvider {
|
struct TransactionAmountTextField_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
TransactionTextField(
|
TransactionAmountTextField(
|
||||||
store: TransactionInputStore(
|
store: TransactionAmountInputStore(
|
||||||
initialState: .init(
|
initialState: .init(
|
||||||
textFieldState: .init(
|
textFieldState: .init(
|
||||||
validationType: .floatingPoint,
|
validationType: .floatingPoint,
|
||||||
|
@ -80,8 +85,8 @@ struct TransactionTextField_Previews: PreviewProvider {
|
||||||
)
|
)
|
||||||
.textFieldTitleAccessoryButtonStyle
|
.textFieldTitleAccessoryButtonStyle
|
||||||
},
|
},
|
||||||
inputAccessoryView: {
|
inputPrefixView: { EmptyView() },
|
||||||
}
|
inputAccessoryView: { EmptyView() }
|
||||||
)
|
)
|
||||||
.preferredColorScheme(.dark)
|
.preferredColorScheme(.dark)
|
||||||
.padding(.horizontal, 50)
|
.padding(.horizontal, 50)
|
||||||
|
@ -92,8 +97,8 @@ struct TransactionTextField_Previews: PreviewProvider {
|
||||||
placeholderText: "",
|
placeholderText: "",
|
||||||
title: "Address",
|
title: "Address",
|
||||||
store: .address,
|
store: .address,
|
||||||
titleAccessoryView: {
|
titleAccessoryView: { EmptyView() },
|
||||||
},
|
inputPrefixView: { EmptyView() },
|
||||||
inputAccessoryView: {
|
inputAccessoryView: {
|
||||||
Image(Asset.Assets.Icons.qrCode.name)
|
Image(Asset.Assets.Icons.qrCode.name)
|
||||||
.resizable()
|
.resizable()
|
|
@ -1,107 +0,0 @@
|
||||||
//
|
|
||||||
// TransactionInputStore.swift
|
|
||||||
// secant-testnet
|
|
||||||
//
|
|
||||||
// Created by Adam Stener on 4/5/22.
|
|
||||||
//
|
|
||||||
|
|
||||||
import ComposableArchitecture
|
|
||||||
|
|
||||||
typealias TransactionInputReducer = Reducer<
|
|
||||||
TransactionInputState,
|
|
||||||
TransactionInputAction,
|
|
||||||
TransactionInputEnvironment
|
|
||||||
>
|
|
||||||
typealias TransactionReducerData = (inout TransactionInputState, TransactionInputAction) -> Void
|
|
||||||
typealias TransactionInputStore = Store<TransactionInputState, TransactionInputAction>
|
|
||||||
|
|
||||||
struct TransactionInputState: Equatable {
|
|
||||||
var textFieldState: TextFieldState
|
|
||||||
var currencySelectionState: CurrencySelectionState
|
|
||||||
var maxValue: Int64 = 0
|
|
||||||
|
|
||||||
var amount: Int64 {
|
|
||||||
Int64((Double(textFieldState.text) ?? 0.0) * 100_000_000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TransactionInputAction: Equatable {
|
|
||||||
case setMax(Int64)
|
|
||||||
case textField(TextFieldAction)
|
|
||||||
case currencySelection(CurrencySelectionAction)
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TransactionInputEnvironment: Equatable {}
|
|
||||||
|
|
||||||
extension TransactionInputReducer {
|
|
||||||
static let `default` = TransactionInputReducer.combine(
|
|
||||||
[
|
|
||||||
textFieldReducer,
|
|
||||||
currencySelectionReducer,
|
|
||||||
maxOverride,
|
|
||||||
currencyUpdate
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
static let maxOverride = TransactionInputReducer { state, action, _ in
|
|
||||||
switch action {
|
|
||||||
case .setMax(let value):
|
|
||||||
state.currencySelectionState.currencyType = .zec
|
|
||||||
state.textFieldState.text = "\(value.asHumanReadableZecBalance())"
|
|
||||||
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
|
|
||||||
return .none
|
|
||||||
}
|
|
||||||
|
|
||||||
static let currencyUpdate = TransactionInputReducer { state, action, _ in
|
|
||||||
switch action {
|
|
||||||
case .currencySelection:
|
|
||||||
guard let currentDoubleValue = Double(state.textFieldState.text) else {
|
|
||||||
return .none
|
|
||||||
}
|
|
||||||
|
|
||||||
let currencyType = state.currencySelectionState.currencyType
|
|
||||||
|
|
||||||
// The 2100 is another hard coded value (🚀🌒) but the store could
|
|
||||||
// have a dependency injected that would be responsible for
|
|
||||||
// providing the current exchange rate.
|
|
||||||
let newValue = currencyType == .zec ?
|
|
||||||
currentDoubleValue / 2100 :
|
|
||||||
currentDoubleValue * 2100
|
|
||||||
state.textFieldState.text = "\(newValue)"
|
|
||||||
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
|
|
||||||
return .none
|
|
||||||
}
|
|
||||||
|
|
||||||
private static let textFieldReducer: TransactionInputReducer = TextFieldReducer.default.pullback(
|
|
||||||
state: \TransactionInputState.textFieldState,
|
|
||||||
action: /TransactionInputAction.textField,
|
|
||||||
environment: { _ in return .init() }
|
|
||||||
)
|
|
||||||
|
|
||||||
private static let currencySelectionReducer: TransactionInputReducer = CurrencySelectionReducer.default.pullback(
|
|
||||||
state: \TransactionInputState.currencySelectionState,
|
|
||||||
action: /TransactionInputAction.currencySelection,
|
|
||||||
environment: { _ in return .init() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TransactionInputState {
|
|
||||||
static let placeholer = TransactionInputState(
|
|
||||||
textFieldState: .placeholder,
|
|
||||||
currencySelectionState: CurrencySelectionState()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
extension TransactionInputStore {
|
|
||||||
static let placeholder = TransactionInputStore(
|
|
||||||
initialState: .placeholer,
|
|
||||||
reducer: .default,
|
|
||||||
environment: TransactionInputEnvironment()
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
//
|
||||||
|
// Double+Zcash.swift
|
||||||
|
// secant-testnet
|
||||||
|
//
|
||||||
|
// Created by Lukáš Korba on 06.05.2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
// TODO: Improve with decimals and zatoshi type, issue #272 (https://github.com/zcash/secant-ios-wallet/issues/272)
|
||||||
|
extension Double {
|
||||||
|
func asZec() -> Int64 {
|
||||||
|
return Int64((self * 100_000_000).rounded())
|
||||||
|
}
|
||||||
|
|
||||||
|
func asZecString() -> String {
|
||||||
|
NumberFormatter.zcashFormatter.string(from: NSNumber(value: self)) ?? ""
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,4 +12,8 @@ extension Int64 {
|
||||||
func asHumanReadableZecBalance() -> Double {
|
func asHumanReadableZecBalance() -> Double {
|
||||||
Double(self) / Double(100_000_000)
|
Double(self) / Double(100_000_000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func asZecString() -> String {
|
||||||
|
NumberFormatter.zcashFormatter.string(from: NSNumber(value: self.asHumanReadableZecBalance())) ?? ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,24 @@ extension String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NumberFormatter {
|
||||||
|
static let zcashFormatter: NumberFormatter = {
|
||||||
|
var formatter = NumberFormatter()
|
||||||
|
formatter.maximumFractionDigits = 8
|
||||||
|
formatter.maximumIntegerDigits = 8
|
||||||
|
formatter.numberStyle = .decimal
|
||||||
|
formatter.usesGroupingSeparator = true
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
var doubleValue: Double? {
|
||||||
|
return NumberFormatter.zcashFormatter.number(from: self)?.doubleValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension String {
|
extension String {
|
||||||
// TODO: Issue #245 Add Validation Regex that support localization
|
|
||||||
private static let floatingPointRegex = "^[0-9]*.?[0-9]+"
|
|
||||||
private static let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}"
|
private static let emailRegex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}"
|
||||||
private static let phoneRegex = "^^\\+(?:[0-9]?){6,14}[0-9]$"
|
private static let phoneRegex = "^^\\+(?:[0-9]?){6,14}[0-9]$"
|
||||||
|
|
||||||
|
@ -32,7 +47,7 @@ extension String {
|
||||||
return text.validate(using: .emailRegex)
|
return text.validate(using: .emailRegex)
|
||||||
|
|
||||||
case .floatingPoint:
|
case .floatingPoint:
|
||||||
return text.validate(using: .floatingPointRegex)
|
return text.doubleValue != nil
|
||||||
|
|
||||||
case .maxLength(let length):
|
case .maxLength(let length):
|
||||||
return text.count <= length && !text.isEmpty
|
return text.count <= length && !text.isEmpty
|
||||||
|
|
|
@ -124,6 +124,11 @@ struct WrappedDerivationTool {
|
||||||
Checks validity of the shielded address.
|
Checks validity of the shielded address.
|
||||||
*/
|
*/
|
||||||
let isValidShieldedAddress: (String) throws -> Bool
|
let isValidShieldedAddress: (String) throws -> Bool
|
||||||
|
|
||||||
|
/**
|
||||||
|
Checks if given address is a valid zcash address.
|
||||||
|
*/
|
||||||
|
let isValidZcashAddress: (String) throws -> Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
extension WrappedDerivationTool {
|
extension WrappedDerivationTool {
|
||||||
|
@ -170,6 +175,10 @@ extension WrappedDerivationTool {
|
||||||
},
|
},
|
||||||
isValidShieldedAddress: { zAddress in
|
isValidShieldedAddress: { zAddress in
|
||||||
try derivationTool.isValidShieldedAddress(zAddress)
|
try derivationTool.isValidShieldedAddress(zAddress)
|
||||||
|
},
|
||||||
|
isValidZcashAddress: { address in
|
||||||
|
try derivationTool.isValidTransparentAddress(address) ? true :
|
||||||
|
try derivationTool.isValidShieldedAddress(address) ? true : false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -313,7 +313,7 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
||||||
mocked.map {
|
mocked.map {
|
||||||
TransactionState.placeholder(
|
TransactionState.placeholder(
|
||||||
date: Date.init(timeIntervalSince1970: $0.date),
|
date: Date.init(timeIntervalSince1970: $0.date),
|
||||||
amount: $0.amount * 100000000,
|
amount: $0.amount,
|
||||||
shielded: $0.shielded,
|
shielded: $0.shielded,
|
||||||
status: $0.status,
|
status: $0.status,
|
||||||
subtitle: $0.subtitle
|
subtitle: $0.subtitle
|
||||||
|
@ -335,7 +335,7 @@ class MockWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
||||||
mocked.map {
|
mocked.map {
|
||||||
TransactionState.placeholder(
|
TransactionState.placeholder(
|
||||||
date: Date.init(timeIntervalSince1970: $0.date),
|
date: Date.init(timeIntervalSince1970: $0.date),
|
||||||
amount: $0.amount * 100000000,
|
amount: $0.amount,
|
||||||
shielded: $0.shielded,
|
shielded: $0.shielded,
|
||||||
status: $0.status,
|
status: $0.status,
|
||||||
subtitle: $0.subtitle
|
subtitle: $0.subtitle
|
||||||
|
@ -420,7 +420,7 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
||||||
mocked.map {
|
mocked.map {
|
||||||
TransactionState.placeholder(
|
TransactionState.placeholder(
|
||||||
date: Date.init(timeIntervalSince1970: $0.date),
|
date: Date.init(timeIntervalSince1970: $0.date),
|
||||||
amount: $0.amount * 100000000,
|
amount: $0.amount,
|
||||||
shielded: $0.shielded,
|
shielded: $0.shielded,
|
||||||
status: $0.status,
|
status: $0.status,
|
||||||
subtitle: $0.subtitle,
|
subtitle: $0.subtitle,
|
||||||
|
@ -443,7 +443,7 @@ class TestWrappedSDKSynchronizer: WrappedSDKSynchronizer {
|
||||||
mocked.map {
|
mocked.map {
|
||||||
TransactionState.placeholder(
|
TransactionState.placeholder(
|
||||||
date: Date.init(timeIntervalSince1970: $0.date),
|
date: Date.init(timeIntervalSince1970: $0.date),
|
||||||
amount: $0.amount * 100000000,
|
amount: $0.amount,
|
||||||
shielded: $0.shielded,
|
shielded: $0.shielded,
|
||||||
status: $0.status,
|
status: $0.status,
|
||||||
subtitle: $0.subtitle,
|
subtitle: $0.subtitle,
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
//
|
||||||
|
// CurrencySelectionTests.swift
|
||||||
|
// secantTests
|
||||||
|
//
|
||||||
|
// Created by Lukáš Korba on 09.05.2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import secant_testnet
|
||||||
|
import ComposableArchitecture
|
||||||
|
|
||||||
|
class CurrencySelectionTests: XCTestCase {
|
||||||
|
func testCurrencySwapUsdToZec() throws {
|
||||||
|
let store = TestStore(
|
||||||
|
initialState: CurrencySelectionState(currencyType: .usd),
|
||||||
|
reducer: CurrencySelectionReducer.default,
|
||||||
|
environment: CurrencySelectionEnvironment()
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.swapCurrencyType) { state in
|
||||||
|
state.currencyType = .zec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCurrencySwapZecToUsd() throws {
|
||||||
|
let store = TestStore(
|
||||||
|
initialState: CurrencySelectionState(currencyType: .zec),
|
||||||
|
reducer: CurrencySelectionReducer.default,
|
||||||
|
environment: CurrencySelectionEnvironment()
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.swapCurrencyType) { state in
|
||||||
|
state.currencyType = .usd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,11 @@ import XCTest
|
||||||
import ComposableArchitecture
|
import ComposableArchitecture
|
||||||
import ZcashLightClientKit
|
import ZcashLightClientKit
|
||||||
|
|
||||||
|
// TODO: these tests will be updated with the Zatoshi/Balance representative once done, issue #272 https://github.com/zcash/secant-ios-wallet/issues/272
|
||||||
|
|
||||||
|
// TODO: these test will be updated with the NumberFormater dependency to handle locale, issue #312 (https://github.com/zcash/secant-ios-wallet/issues/312)
|
||||||
|
|
||||||
|
// swiftlint:disable type_body_length
|
||||||
class SendTests: XCTestCase {
|
class SendTests: XCTestCase {
|
||||||
var storage = WalletStorage(secItem: .live)
|
var storage = WalletStorage(secItem: .live)
|
||||||
|
|
||||||
|
@ -117,4 +122,389 @@ class SendTests: XCTestCase {
|
||||||
state.route = .failure
|
state.route = .failure
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testAddressValidation() throws {
|
||||||
|
let testScheduler = DispatchQueue.test
|
||||||
|
|
||||||
|
let testEnvironment = SendEnvironment(
|
||||||
|
mnemonicSeedPhraseProvider: .mock,
|
||||||
|
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||||
|
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||||
|
wrappedDerivationTool: .live(),
|
||||||
|
wrappedSDKSynchronizer: TestWrappedSDKSynchronizer()
|
||||||
|
)
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState: .placeholder,
|
||||||
|
reducer: SendReducer.default,
|
||||||
|
environment: testEnvironment
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.transactionAddressInput(.textField(.set("3HRG769ii3HDSJV5vNknQPzXqtL2mTSGnr")))) { state in
|
||||||
|
state.transactionAddressInputState.textFieldState.text = "3HRG769ii3HDSJV5vNknQPzXqtL2mTSGnr"
|
||||||
|
// true is expected here because textField doesn't have any `validationType: String.ValidationType?`
|
||||||
|
// isValid function returns true, `guard let validationType = validationType else { return true }`
|
||||||
|
state.transactionAddressInputState.textFieldState.valid = true
|
||||||
|
state.transactionAddressInputState.isValidAddress = false
|
||||||
|
XCTAssertTrue(
|
||||||
|
state.isInvalidAddressFormat,
|
||||||
|
"Send Tests: `testAddressValidation` is expected to be true but it's \(state.isInvalidAddressFormat)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
store.send(.transactionAddressInput(.textField(.set("t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po")))) { state in
|
||||||
|
state.transactionAddressInputState.textFieldState.text = "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po"
|
||||||
|
// true is expected here because textField doesn't have any `validationType: String.ValidationType?`
|
||||||
|
// isValid function returns true, `guard let validationType = validationType else { return true }`
|
||||||
|
state.transactionAddressInputState.textFieldState.valid = true
|
||||||
|
state.transactionAddressInputState.isValidAddress = true
|
||||||
|
XCTAssertFalse(
|
||||||
|
state.isInvalidAddressFormat,
|
||||||
|
"Send Tests: `testAddressValidation` is expected to be false but it's \(state.isInvalidAddressFormat)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInvalidAmountFormatEmptyInput() throws {
|
||||||
|
let testScheduler = DispatchQueue.test
|
||||||
|
|
||||||
|
let testEnvironment = SendEnvironment(
|
||||||
|
mnemonicSeedPhraseProvider: .mock,
|
||||||
|
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||||
|
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||||
|
wrappedDerivationTool: .live(),
|
||||||
|
wrappedSDKSynchronizer: TestWrappedSDKSynchronizer()
|
||||||
|
)
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState: .placeholder,
|
||||||
|
reducer: SendReducer.default,
|
||||||
|
environment: testEnvironment
|
||||||
|
)
|
||||||
|
|
||||||
|
// Checks the computed property `isInvalidAmountFormat` which controls the error message to be shown on the screen
|
||||||
|
// With empty input it must be false
|
||||||
|
store.send(.transactionAmountInput(.textField(.set("")))) { state in
|
||||||
|
state.transactionAmountInputState.textFieldState.text = ""
|
||||||
|
state.transactionAmountInputState.textFieldState.valid = false
|
||||||
|
XCTAssertFalse(
|
||||||
|
state.isInvalidAmountFormat,
|
||||||
|
"Send Tests: `testInvalidAmountFormatEmptyInput` is expected to be false but it's \(state.isInvalidAmountFormat)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInvalidAddressFormatEmptyInput() throws {
|
||||||
|
let testScheduler = DispatchQueue.test
|
||||||
|
|
||||||
|
let testEnvironment = SendEnvironment(
|
||||||
|
mnemonicSeedPhraseProvider: .mock,
|
||||||
|
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||||
|
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||||
|
wrappedDerivationTool: .live(),
|
||||||
|
wrappedSDKSynchronizer: TestWrappedSDKSynchronizer()
|
||||||
|
)
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState: .placeholder,
|
||||||
|
reducer: SendReducer.default,
|
||||||
|
environment: testEnvironment
|
||||||
|
)
|
||||||
|
|
||||||
|
// Checks the computed property `isInvalidAddressFormat` which controls the error message to be shown on the screen
|
||||||
|
// With empty input it must be false
|
||||||
|
store.send(.transactionAddressInput(.textField(.set("")))) { state in
|
||||||
|
state.transactionAddressInputState.textFieldState.text = ""
|
||||||
|
// true is expected here because textField doesn't have any `validationType: String.ValidationType?`
|
||||||
|
// isValid function returns true, `guard let validationType = validationType else { return true }`
|
||||||
|
state.transactionAddressInputState.textFieldState.valid = true
|
||||||
|
XCTAssertFalse(
|
||||||
|
state.isInvalidAddressFormat,
|
||||||
|
"Send Tests: `testInvalidAddressFormatEmptyInput` is expected to be false but it's \(state.isInvalidAddressFormat)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFundsSufficiency() throws {
|
||||||
|
try XCTSkipUnless(Locale.current.regionCode == "US", "testFundsSufficiency is designed to test US locale only")
|
||||||
|
|
||||||
|
let sendState = SendState(
|
||||||
|
transaction: .placeholder,
|
||||||
|
transactionAddressInputState: .placeholder,
|
||||||
|
transactionAmountInputState:
|
||||||
|
TransactionAmountInputState(
|
||||||
|
textFieldState: .amount,
|
||||||
|
currencySelectionState: CurrencySelectionState(),
|
||||||
|
maxValue: 501_300
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
let testScheduler = DispatchQueue.test
|
||||||
|
|
||||||
|
let testEnvironment = SendEnvironment(
|
||||||
|
mnemonicSeedPhraseProvider: .mock,
|
||||||
|
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||||
|
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||||
|
wrappedDerivationTool: .live(),
|
||||||
|
wrappedSDKSynchronizer: TestWrappedSDKSynchronizer()
|
||||||
|
)
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState: sendState,
|
||||||
|
reducer: SendReducer.default,
|
||||||
|
environment: testEnvironment
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.transactionAmountInput(.textField(.set("0.00501299")))) { state in
|
||||||
|
state.transactionAmountInputState.textFieldState.text = "0.00501299"
|
||||||
|
state.transactionAmountInputState.textFieldState.valid = true
|
||||||
|
XCTAssertFalse(
|
||||||
|
state.isInsufficientFunds,
|
||||||
|
"Send Tests: `testFundsSufficiency` is expected to be false but it's \(state.isInsufficientFunds)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
store.send(.transactionAmountInput(.textField(.set("0.00501301")))) { state in
|
||||||
|
state.transactionAmountInputState.textFieldState.text = "0.00501301"
|
||||||
|
state.transactionAmountInputState.textFieldState.valid = true
|
||||||
|
XCTAssertTrue(
|
||||||
|
state.isInsufficientFunds,
|
||||||
|
"Send Tests: `testFundsSufficiency` is expected to be true but it's \(state.isInsufficientFunds)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDifferentAmountFormats() throws {
|
||||||
|
try XCTSkipUnless(Locale.current.regionCode == "US", "testDifferentAmountFormats is designed to test US locale only")
|
||||||
|
|
||||||
|
let testScheduler = DispatchQueue.test
|
||||||
|
|
||||||
|
let testEnvironment = SendEnvironment(
|
||||||
|
mnemonicSeedPhraseProvider: .mock,
|
||||||
|
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||||
|
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||||
|
wrappedDerivationTool: .live(),
|
||||||
|
wrappedSDKSynchronizer: TestWrappedSDKSynchronizer()
|
||||||
|
)
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState: .placeholder,
|
||||||
|
reducer: SendReducer.default,
|
||||||
|
environment: testEnvironment
|
||||||
|
)
|
||||||
|
|
||||||
|
try amountFormatTest("1.234", true, 123_400_000, store)
|
||||||
|
try amountFormatTest("1,234", true, 123_400_000_000, store)
|
||||||
|
try amountFormatTest("1 234", true, 123_400_000_000, store)
|
||||||
|
try amountFormatTest("1,234.567", true, 123_456_700_000, store)
|
||||||
|
try amountFormatTest("1.", true, 100_000_000, store)
|
||||||
|
try amountFormatTest("1..", false, 0, store)
|
||||||
|
try amountFormatTest("1,.", false, 0, store)
|
||||||
|
try amountFormatTest("1.,", false, 0, store)
|
||||||
|
try amountFormatTest("1,,", false, 0, store)
|
||||||
|
try amountFormatTest("1,23", false, 0, store)
|
||||||
|
try amountFormatTest("1 23", false, 0, store)
|
||||||
|
try amountFormatTest("1.2.3", false, 0, store)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testValidForm() throws {
|
||||||
|
try XCTSkipUnless(Locale.current.regionCode == "US", "testValidForm is designed to test US locale only")
|
||||||
|
|
||||||
|
let sendState = SendState(
|
||||||
|
transaction: .placeholder,
|
||||||
|
transactionAddressInputState: .placeholder,
|
||||||
|
transactionAmountInputState:
|
||||||
|
TransactionAmountInputState(
|
||||||
|
textFieldState:
|
||||||
|
TextFieldState(
|
||||||
|
validationType: .floatingPoint,
|
||||||
|
text: "0.00501301"
|
||||||
|
),
|
||||||
|
currencySelectionState: CurrencySelectionState(),
|
||||||
|
maxValue: 501_302
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
let testScheduler = DispatchQueue.test
|
||||||
|
|
||||||
|
let testEnvironment = SendEnvironment(
|
||||||
|
mnemonicSeedPhraseProvider: .mock,
|
||||||
|
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||||
|
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||||
|
wrappedDerivationTool: .live(),
|
||||||
|
wrappedSDKSynchronizer: TestWrappedSDKSynchronizer()
|
||||||
|
)
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState: sendState,
|
||||||
|
reducer: SendReducer.default,
|
||||||
|
environment: testEnvironment
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.transactionAddressInput(.textField(.set("t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po")))) { state in
|
||||||
|
state.transactionAddressInputState.textFieldState.text = "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po"
|
||||||
|
// true is expected here because textField doesn't have any `validationType: String.ValidationType?`
|
||||||
|
// isValid function returns true, `guard let validationType = validationType else { return true }`
|
||||||
|
state.transactionAddressInputState.textFieldState.valid = true
|
||||||
|
state.transactionAddressInputState.isValidAddress = true
|
||||||
|
XCTAssertTrue(
|
||||||
|
state.isValidForm,
|
||||||
|
"Send Tests: `testValidForm` is expected to be true but it's \(state.isValidForm)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInvalidForm_InsufficientFunds() throws {
|
||||||
|
let sendState = SendState(
|
||||||
|
transaction: .placeholder,
|
||||||
|
transactionAddressInputState: .placeholder,
|
||||||
|
transactionAmountInputState:
|
||||||
|
TransactionAmountInputState(
|
||||||
|
textFieldState:
|
||||||
|
TextFieldState(
|
||||||
|
validationType: .floatingPoint,
|
||||||
|
text: "0.00501301"
|
||||||
|
),
|
||||||
|
currencySelectionState: CurrencySelectionState(),
|
||||||
|
maxValue: 501_300
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
let testScheduler = DispatchQueue.test
|
||||||
|
|
||||||
|
let testEnvironment = SendEnvironment(
|
||||||
|
mnemonicSeedPhraseProvider: .mock,
|
||||||
|
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||||
|
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||||
|
wrappedDerivationTool: .live(),
|
||||||
|
wrappedSDKSynchronizer: TestWrappedSDKSynchronizer()
|
||||||
|
)
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState: sendState,
|
||||||
|
reducer: SendReducer.default,
|
||||||
|
environment: testEnvironment
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.transactionAddressInput(.textField(.set("t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po")))) { state in
|
||||||
|
state.transactionAddressInputState.textFieldState.text = "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po"
|
||||||
|
// true is expected here because textField doesn't have any `validationType: String.ValidationType?`
|
||||||
|
// isValid function returns true, `guard let validationType = validationType else { return true }`
|
||||||
|
state.transactionAddressInputState.textFieldState.valid = true
|
||||||
|
state.transactionAddressInputState.isValidAddress = true
|
||||||
|
XCTAssertFalse(
|
||||||
|
state.isValidForm,
|
||||||
|
"Send Tests: `testValidForm` is expected to be false but it's \(state.isValidForm)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInvalidForm_AddressFormat() throws {
|
||||||
|
let sendState = SendState(
|
||||||
|
transaction: .placeholder,
|
||||||
|
transactionAddressInputState: .placeholder,
|
||||||
|
transactionAmountInputState:
|
||||||
|
TransactionAmountInputState(
|
||||||
|
textFieldState:
|
||||||
|
TextFieldState(
|
||||||
|
validationType: .floatingPoint,
|
||||||
|
text: "0.00501301"
|
||||||
|
),
|
||||||
|
currencySelectionState: CurrencySelectionState(),
|
||||||
|
maxValue: 501_302
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
let testScheduler = DispatchQueue.test
|
||||||
|
|
||||||
|
let testEnvironment = SendEnvironment(
|
||||||
|
mnemonicSeedPhraseProvider: .mock,
|
||||||
|
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||||
|
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||||
|
wrappedDerivationTool: .live(),
|
||||||
|
wrappedSDKSynchronizer: TestWrappedSDKSynchronizer()
|
||||||
|
)
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState: sendState,
|
||||||
|
reducer: SendReducer.default,
|
||||||
|
environment: testEnvironment
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.transactionAddressInput(.textField(.set("3HRG769ii3HDSJV5vNknQPzXqtL2mTSGnr")))) { state in
|
||||||
|
state.transactionAddressInputState.textFieldState.text = "3HRG769ii3HDSJV5vNknQPzXqtL2mTSGnr"
|
||||||
|
// true is expected here because textField doesn't have any `validationType: String.ValidationType?`
|
||||||
|
// isValid function returns true, `guard let validationType = validationType else { return true }`
|
||||||
|
state.transactionAddressInputState.textFieldState.valid = true
|
||||||
|
state.transactionAddressInputState.isValidAddress = false
|
||||||
|
XCTAssertFalse(
|
||||||
|
state.isValidForm,
|
||||||
|
"Send Tests: `testValidForm` is expected to be false but it's \(state.isValidForm)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInvalidForm_AmountFormat() throws {
|
||||||
|
let sendState = SendState(
|
||||||
|
transaction: .placeholder,
|
||||||
|
transactionAddressInputState: .placeholder,
|
||||||
|
transactionAmountInputState:
|
||||||
|
TransactionAmountInputState(
|
||||||
|
textFieldState:
|
||||||
|
TextFieldState(
|
||||||
|
validationType: .floatingPoint,
|
||||||
|
text: "0.0.0501301"
|
||||||
|
),
|
||||||
|
currencySelectionState: CurrencySelectionState(),
|
||||||
|
maxValue: 501_302
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
let testScheduler = DispatchQueue.test
|
||||||
|
|
||||||
|
let testEnvironment = SendEnvironment(
|
||||||
|
mnemonicSeedPhraseProvider: .mock,
|
||||||
|
scheduler: testScheduler.eraseToAnyScheduler(),
|
||||||
|
walletStorage: .live(walletStorage: WalletStorage(secItem: .live)),
|
||||||
|
wrappedDerivationTool: .live(),
|
||||||
|
wrappedSDKSynchronizer: TestWrappedSDKSynchronizer()
|
||||||
|
)
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState: sendState,
|
||||||
|
reducer: SendReducer.default,
|
||||||
|
environment: testEnvironment
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.transactionAddressInput(.textField(.set("t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po")))) { state in
|
||||||
|
state.transactionAddressInputState.textFieldState.text = "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po"
|
||||||
|
// true is expected here because textField doesn't have any `validationType: String.ValidationType?`
|
||||||
|
// isValid function returns true, `guard let validationType = validationType else { return true }`
|
||||||
|
state.transactionAddressInputState.textFieldState.valid = true
|
||||||
|
state.transactionAddressInputState.isValidAddress = true
|
||||||
|
XCTAssertFalse(
|
||||||
|
state.isValidForm,
|
||||||
|
"Send Tests: `testValidForm` is expected to be false but it's \(state.isValidForm)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension SendTests {
|
||||||
|
func amountFormatTest(
|
||||||
|
_ amount: String,
|
||||||
|
_ expectedValidationResult: Bool,
|
||||||
|
_ expectedAmount: Int64,
|
||||||
|
_ store: TestStore<SendState, SendState, SendAction, SendAction, SendEnvironment>
|
||||||
|
) throws {
|
||||||
|
store.send(.transactionAmountInput(.textField(.set(amount)))) { state in
|
||||||
|
state.transactionAmountInputState.textFieldState.text = amount
|
||||||
|
state.transactionAmountInputState.textFieldState.valid = expectedValidationResult
|
||||||
|
XCTAssertEqual(
|
||||||
|
expectedAmount,
|
||||||
|
state.transactionAmountInputState.amount,
|
||||||
|
"Send Tests: `amountFormatTest` expected amount is \(expectedAmount) but result is \(state.isInvalidAddressFormat)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// TransactionAddressInputTests.swift
|
||||||
|
// secantTests
|
||||||
|
//
|
||||||
|
// Created by Lukáš Korba on 06.05.2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import secant_testnet
|
||||||
|
import ComposableArchitecture
|
||||||
|
|
||||||
|
class TransactionAddressInputTests: XCTestCase {
|
||||||
|
func testClearValue() throws {
|
||||||
|
let store = TestStore(
|
||||||
|
initialState:
|
||||||
|
TransactionAddressInputState(
|
||||||
|
textFieldState:
|
||||||
|
TextFieldState(
|
||||||
|
validationType: nil,
|
||||||
|
text: "t1gXqfSSQt6WfpwyuCU3Wi7sSVZ66DYQ3Po"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
reducer: TransactionAddressInputReducer.default,
|
||||||
|
environment:
|
||||||
|
TransactionAddressInputEnvironment(
|
||||||
|
wrappedDerivationTool: .live()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.clearAddress) { state in
|
||||||
|
state.textFieldState.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
//
|
||||||
|
// TransactionAmountInputTests.swift
|
||||||
|
// secantTests
|
||||||
|
//
|
||||||
|
// Created by Lukáš Korba on 06.05.2022.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import secant_testnet
|
||||||
|
import ComposableArchitecture
|
||||||
|
|
||||||
|
// TODO: these tests will be updated with the Zatoshi/Balance representative once done, issue #272 https://github.com/zcash/secant-ios-wallet/issues/272
|
||||||
|
|
||||||
|
// TODO: these test will be updated with the NumberFormater dependency to handle locale, issue #312 (https://github.com/zcash/secant-ios-wallet/issues/312)
|
||||||
|
|
||||||
|
class TransactionAmountInputTests: XCTestCase {
|
||||||
|
func testMaxValue() throws {
|
||||||
|
try XCTSkipUnless(Locale.current.regionCode == "US", "testMaxValue is designed to test US locale only")
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState:
|
||||||
|
TransactionAmountInputState(
|
||||||
|
textFieldState:
|
||||||
|
TextFieldState(
|
||||||
|
validationType: .floatingPoint,
|
||||||
|
text: "0.002"
|
||||||
|
),
|
||||||
|
currencySelectionState: CurrencySelectionState(),
|
||||||
|
maxValue: 501_301
|
||||||
|
),
|
||||||
|
reducer: TransactionAmountInputReducer.default,
|
||||||
|
environment: TransactionAmountInputEnvironment()
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.setMax) { state in
|
||||||
|
state.textFieldState.text = "0.00501301"
|
||||||
|
XCTAssertEqual(501_301, state.amount, "AmountInput Tests: `testMaxValue` expected \(501_301) but received \(state.amount)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testClearValue() throws {
|
||||||
|
let store = TestStore(
|
||||||
|
initialState:
|
||||||
|
TransactionAmountInputState(
|
||||||
|
textFieldState:
|
||||||
|
TextFieldState(
|
||||||
|
validationType: .floatingPoint,
|
||||||
|
text: "0.002"
|
||||||
|
),
|
||||||
|
currencySelectionState: CurrencySelectionState(),
|
||||||
|
maxValue: 501_301
|
||||||
|
),
|
||||||
|
reducer: TransactionAmountInputReducer.default,
|
||||||
|
environment: TransactionAmountInputEnvironment()
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.clearValue) { state in
|
||||||
|
state.textFieldState.text = ""
|
||||||
|
XCTAssertEqual(0, state.amount, "AmountInput Tests: `testClearValue` expected \(0) but received \(state.amount)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testZecUsdConvertedAmount() throws {
|
||||||
|
try XCTSkipUnless(Locale.current.regionCode == "US", "testZecUsdConvertedAmount is designed to test US locale only")
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState:
|
||||||
|
TransactionAmountInputState(
|
||||||
|
textFieldState:
|
||||||
|
TextFieldState(
|
||||||
|
validationType: .floatingPoint,
|
||||||
|
text: "1.0"
|
||||||
|
),
|
||||||
|
currencySelectionState:
|
||||||
|
CurrencySelectionState(
|
||||||
|
currencyType: .zec
|
||||||
|
),
|
||||||
|
zecPrice: 1000.0
|
||||||
|
),
|
||||||
|
reducer: TransactionAmountInputReducer.default,
|
||||||
|
environment: TransactionAmountInputEnvironment()
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.currencySelection(.swapCurrencyType)) { state in
|
||||||
|
state.textFieldState.text = "1,000"
|
||||||
|
state.currencySelectionState.currencyType = .usd
|
||||||
|
XCTAssertEqual(
|
||||||
|
100_000_000,
|
||||||
|
state.amount,
|
||||||
|
"AmountInput Tests: `testZecUsdConvertedAmount` expected \(100_000_000) but received \(state.amount)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testUsdZecConvertedAmount() throws {
|
||||||
|
try XCTSkipUnless(Locale.current.regionCode == "US", "testUsdZecConvertedAmount is designed to test US locale only")
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState:
|
||||||
|
TransactionAmountInputState(
|
||||||
|
textFieldState:
|
||||||
|
TextFieldState(
|
||||||
|
validationType: .floatingPoint,
|
||||||
|
text: "1 000"
|
||||||
|
),
|
||||||
|
currencySelectionState:
|
||||||
|
CurrencySelectionState(
|
||||||
|
currencyType: .usd
|
||||||
|
),
|
||||||
|
zecPrice: 1000.0
|
||||||
|
),
|
||||||
|
reducer: TransactionAmountInputReducer.default,
|
||||||
|
environment: TransactionAmountInputEnvironment()
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.currencySelection(.swapCurrencyType)) { state in
|
||||||
|
state.textFieldState.text = "1"
|
||||||
|
state.currencySelectionState.currencyType = .zec
|
||||||
|
XCTAssertEqual(
|
||||||
|
100_000_000,
|
||||||
|
state.amount,
|
||||||
|
"AmountInput Tests: `testZecUsdConvertedAmount` expected \(100_000_000) but received \(state.amount)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testIfAmountIsMax() throws {
|
||||||
|
try XCTSkipUnless(Locale.current.regionCode == "US", "testIfAmountIsMax is designed to test US locale only")
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState:
|
||||||
|
TransactionAmountInputState(
|
||||||
|
textFieldState:
|
||||||
|
TextFieldState(
|
||||||
|
validationType: .floatingPoint,
|
||||||
|
text: "5"
|
||||||
|
),
|
||||||
|
currencySelectionState:
|
||||||
|
CurrencySelectionState(
|
||||||
|
currencyType: .usd
|
||||||
|
),
|
||||||
|
maxValue: 100_000_000,
|
||||||
|
zecPrice: 1000.0
|
||||||
|
),
|
||||||
|
reducer: TransactionAmountInputReducer.default,
|
||||||
|
environment: TransactionAmountInputEnvironment()
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.textField(.set("1 000"))) { state in
|
||||||
|
state.textFieldState.text = "1 000"
|
||||||
|
state.textFieldState.valid = true
|
||||||
|
state.currencySelectionState.currencyType = .usd
|
||||||
|
XCTAssertTrue(
|
||||||
|
state.isMax,
|
||||||
|
"AmountInput Tests: `testIfAmountIsMax` is expected to be true but it's \(state.isMax)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMaxZecValue() throws {
|
||||||
|
try XCTSkipUnless(Locale.current.regionCode == "US", "testMaxZecValue is designed to test US locale only")
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState:
|
||||||
|
TransactionAmountInputState(
|
||||||
|
textFieldState:
|
||||||
|
TextFieldState(
|
||||||
|
validationType: .floatingPoint,
|
||||||
|
text: "5"
|
||||||
|
),
|
||||||
|
currencySelectionState:
|
||||||
|
CurrencySelectionState(
|
||||||
|
currencyType: .zec
|
||||||
|
),
|
||||||
|
maxValue: 200_000_000,
|
||||||
|
zecPrice: 1000.0
|
||||||
|
),
|
||||||
|
reducer: TransactionAmountInputReducer.default,
|
||||||
|
environment: TransactionAmountInputEnvironment()
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.setMax) { state in
|
||||||
|
state.textFieldState.text = "2"
|
||||||
|
XCTAssertEqual(
|
||||||
|
200_000_000,
|
||||||
|
state.maxCurrencyConvertedValue,
|
||||||
|
"AmountInput Tests: `testMaxZecValue` expected \(200_000_000) but received \(state.maxCurrencyConvertedValue)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMaxUsdValue() throws {
|
||||||
|
try XCTSkipUnless(Locale.current.regionCode == "US", "testMaxUsdValue is designed to test US locale only")
|
||||||
|
|
||||||
|
let store = TestStore(
|
||||||
|
initialState:
|
||||||
|
TransactionAmountInputState(
|
||||||
|
textFieldState:
|
||||||
|
TextFieldState(
|
||||||
|
validationType: .floatingPoint,
|
||||||
|
text: "5"
|
||||||
|
),
|
||||||
|
currencySelectionState:
|
||||||
|
CurrencySelectionState(
|
||||||
|
currencyType: .usd
|
||||||
|
),
|
||||||
|
maxValue: 200_000_000,
|
||||||
|
zecPrice: 1000.0
|
||||||
|
),
|
||||||
|
reducer: TransactionAmountInputReducer.default,
|
||||||
|
environment: TransactionAmountInputEnvironment()
|
||||||
|
)
|
||||||
|
|
||||||
|
store.send(.setMax) { state in
|
||||||
|
state.textFieldState.text = "2,000"
|
||||||
|
XCTAssertEqual(
|
||||||
|
200_000_000_000,
|
||||||
|
state.maxCurrencyConvertedValue,
|
||||||
|
"AmountInput Tests: `testMaxUsdValue` expected \(200_000_000_000) but received \(state.maxCurrencyConvertedValue)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -52,7 +52,7 @@ class TransactionHistoryTests: XCTestCase {
|
||||||
let transactions = mocked.map {
|
let transactions = mocked.map {
|
||||||
TransactionState.placeholder(
|
TransactionState.placeholder(
|
||||||
date: Date.init(timeIntervalSince1970: $0.date),
|
date: Date.init(timeIntervalSince1970: $0.date),
|
||||||
amount: $0.amount * 100000000,
|
amount: $0.amount,
|
||||||
shielded: $0.shielded,
|
shielded: $0.shielded,
|
||||||
status: $0.status,
|
status: $0.status,
|
||||||
subtitle: $0.subtitle,
|
subtitle: $0.subtitle,
|
||||||
|
|
Loading…
Reference in New Issue