diff --git a/secant.xcodeproj/project.pbxproj b/secant.xcodeproj/project.pbxproj index c04273e..4f44f49 100644 --- a/secant.xcodeproj/project.pbxproj +++ b/secant.xcodeproj/project.pbxproj @@ -57,10 +57,20 @@ 0DFE93E1272C9ECB000FCCA5 /* RecoveryPhraseBackupValidationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFE93E0272C9ECB000FCCA5 /* RecoveryPhraseBackupValidationView.swift */; }; 0DFE93E3272CA1AA000FCCA5 /* RecoveryPhraseValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFE93E2272CA1AA000FCCA5 /* RecoveryPhraseValidation.swift */; }; 0DFE93E6272CB6F7000FCCA5 /* RecoveryPhraseValidationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DFE93E5272CB6F7000FCCA5 /* RecoveryPhraseValidationTests.swift */; }; + 2E35F99227B28E7600EB79CD /* SingleLineTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E35F99127B28E7600EB79CD /* SingleLineTextField.swift */; }; + 2E35F99A27B3E99C00EB79CD /* TextFieldTitleAccessoryButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E35F99927B3E99C00EB79CD /* TextFieldTitleAccessoryButtonStyle.swift */; }; 2E58E73B274679F000B2B84B /* OnboardingHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E58E73A274679F000B2B84B /* OnboardingHeaderView.swift */; }; + 2E6CF8DD27D78319004DCD7A /* CurrencySelectionStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E6CF8DC27D78319004DCD7A /* CurrencySelectionStore.swift */; }; + 2E8719CB27FB09990082C926 /* TransactionTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8719CA27FB09990082C926 /* TransactionTextField.swift */; }; + 2E8719CD27FB0D3B0082C926 /* TransactionCurrencySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E8719CC27FB0D3B0082C926 /* TransactionCurrencySelector.swift */; }; 2EA11F5B27467EF800709571 /* OnboardingFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA11F5A27467EF800709571 /* OnboardingFooterView.swift */; }; 2EA11F5D27467F7700709571 /* OnboardingContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA11F5C27467F7700709571 /* OnboardingContentView.swift */; }; + 2EB1C5E827D77F6100BC43D7 /* TextFieldStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EB1C5E727D77F6100BC43D7 /* TextFieldStore.swift */; }; 2EB660E02747EAB900A06A07 /* OnboardingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2E5C03802738C570008BFFD3 /* OnboardingScreen.swift */; }; + 2EB7758727FC67FD00269373 /* TransactionInputStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EB7758627FC67FD00269373 /* TransactionInputStore.swift */; }; + 2EDA07A027EDE18C00D6F09B /* TextFieldInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EDA079F27EDE18C00D6F09B /* TextFieldInput.swift */; }; + 2EDA07A227EDE1AE00D6F09B /* TextFieldFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EDA07A127EDE1AE00D6F09B /* TextFieldFooter.swift */; }; + 2EDA07A427EDE2A900D6F09B /* DebugFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EDA07A327EDE2A900D6F09B /* DebugFrame.swift */; }; 660558E9270C7A54009D6954 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 660558E8270C7A54009D6954 /* Colors.xcassets */; }; 660558F7270C862F009D6954 /* Fonts+Generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 660558F5270C862F009D6954 /* Fonts+Generated.swift */; }; 660558F8270C862F009D6954 /* XCAssets+Generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 660558F6270C862F009D6954 /* XCAssets+Generated.swift */; }; @@ -92,9 +102,9 @@ 9EF8135C27ECC25E0075AF48 /* WalletStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */; }; 9EF8135D27ECC25E0075AF48 /* UserPreferencesStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */; }; 9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8135F27F043CC0075AF48 /* AppDelegate.swift */; }; - 9EF8139C27F47AED0075AF48 /* InitializationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8139B27F47AED0075AF48 /* InitializationState.swift */; }; 9EF8139127F191BF0075AF48 /* WalletStorageInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8139027F191BF0075AF48 /* WalletStorageInteractor.swift */; }; 9EF8139827F1FAEC0075AF48 /* ZcashLightClientKit in Frameworks */ = {isa = PBXBuildFile; productRef = 9EF8139727F1FAEC0075AF48 /* ZcashLightClientKit */; }; + 9EF8139C27F47AED0075AF48 /* InitializationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EF8139B27F47AED0075AF48 /* InitializationState.swift */; }; F9322DC0273B555C00C105B5 /* NavigationLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9322DBF273B555C00C105B5 /* NavigationLinks.swift */; }; F93673D62742CB840099C6AF /* Previews.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93673D52742CB840099C6AF /* Previews.swift */; }; F93874F0273C4DE200F0E875 /* HomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93874ED273C4DE200F0E875 /* HomeStore.swift */; }; @@ -198,10 +208,20 @@ 0DFE93E0272C9ECB000FCCA5 /* RecoveryPhraseBackupValidationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseBackupValidationView.swift; sourceTree = ""; }; 0DFE93E2272CA1AA000FCCA5 /* RecoveryPhraseValidation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseValidation.swift; sourceTree = ""; }; 0DFE93E5272CB6F7000FCCA5 /* RecoveryPhraseValidationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseValidationTests.swift; sourceTree = ""; }; + 2E35F99127B28E7600EB79CD /* SingleLineTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleLineTextField.swift; sourceTree = ""; }; + 2E35F99927B3E99C00EB79CD /* TextFieldTitleAccessoryButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldTitleAccessoryButtonStyle.swift; sourceTree = ""; }; 2E58E73A274679F000B2B84B /* OnboardingHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingHeaderView.swift; sourceTree = ""; }; 2E5C03802738C570008BFFD3 /* OnboardingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingScreen.swift; sourceTree = ""; }; + 2E6CF8DC27D78319004DCD7A /* CurrencySelectionStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencySelectionStore.swift; sourceTree = ""; }; + 2E8719CA27FB09990082C926 /* TransactionTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionTextField.swift; sourceTree = ""; }; + 2E8719CC27FB0D3B0082C926 /* TransactionCurrencySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionCurrencySelector.swift; sourceTree = ""; }; 2EA11F5A27467EF800709571 /* OnboardingFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingFooterView.swift; sourceTree = ""; }; 2EA11F5C27467F7700709571 /* OnboardingContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingContentView.swift; sourceTree = ""; }; + 2EB1C5E727D77F6100BC43D7 /* TextFieldStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldStore.swift; sourceTree = ""; }; + 2EB7758627FC67FD00269373 /* TransactionInputStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionInputStore.swift; sourceTree = ""; }; + 2EDA079F27EDE18C00D6F09B /* TextFieldInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldInput.swift; sourceTree = ""; }; + 2EDA07A127EDE1AE00D6F09B /* TextFieldFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldFooter.swift; sourceTree = ""; }; + 2EDA07A327EDE2A900D6F09B /* DebugFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugFrame.swift; sourceTree = ""; }; 660558E8270C7A54009D6954 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; }; 660558F5270C862F009D6954 /* Fonts+Generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Fonts+Generated.swift"; sourceTree = ""; }; 660558F6270C862F009D6954 /* XCAssets+Generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "XCAssets+Generated.swift"; sourceTree = ""; }; @@ -232,8 +252,8 @@ 9EF8135A27ECC25E0075AF48 /* WalletStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletStorageTests.swift; sourceTree = ""; }; 9EF8135B27ECC25E0075AF48 /* UserPreferencesStorageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserPreferencesStorageTests.swift; sourceTree = ""; }; 9EF8135F27F043CC0075AF48 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 9EF8139B27F47AED0075AF48 /* InitializationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializationState.swift; sourceTree = ""; }; 9EF8139027F191BF0075AF48 /* WalletStorageInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletStorageInteractor.swift; sourceTree = ""; }; + 9EF8139B27F47AED0075AF48 /* InitializationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitializationState.swift; sourceTree = ""; }; F9322DBF273B555C00C105B5 /* NavigationLinks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationLinks.swift; sourceTree = ""; }; F93673D52742CB840099C6AF /* Previews.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Previews.swift; sourceTree = ""; }; F93874ED273C4DE200F0E875 /* HomeStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeStore.swift; sourceTree = ""; }; @@ -391,6 +411,8 @@ 0D4E7A0726B364170058B01E /* secant */ = { isa = PBXGroup; children = ( + 0D4E7A0826B364170058B01E /* SecantApp.swift */, + 0D4E7A0A26B364170058B01E /* ContentView.swift */, 0DACFA7D27208CC80039EEA5 /* Util */, 6654C73B2715A3F000901167 /* Features */, 660558F4270C85F7009D6954 /* Generated */, @@ -481,6 +503,7 @@ 0DA13C9126C15E1900E3B610 /* UIComponents */ = { isa = PBXGroup; children = ( + 2E35F99027B28E6800EB79CD /* TextFields */, 0D0781C5278776B90083ACD7 /* Shapes */, 0D8A43C2272AEEA7005A6414 /* FontStyles */, 669FDAE7272C239D007B9422 /* CircularFrame */, @@ -513,6 +536,7 @@ F9EEB8152742C2210032EEB8 /* WithStateBinding.swift */, F93673D52742CB840099C6AF /* Previews.swift */, 0D35CC45277A36E00074316A /* ScrollableWhenScaled.swift */, + 2EDA07A327EDE2A900D6F09B /* DebugFrame.swift */, 9E2AC10027D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift */, 9E2AC10227DA28200042AA47 /* WalletStorage.swift */, 9E80B47127E4B34B008FF493 /* UserPreferencesStorage.swift */, @@ -567,6 +591,18 @@ path = RecoveryPhraseValidationTests; sourceTree = ""; }; + 2E35F99027B28E6800EB79CD /* TextFields */ = { + isa = PBXGroup; + children = ( + 2E8719CA27FB09990082C926 /* TransactionTextField.swift */, + 2E8719CC27FB0D3B0082C926 /* TransactionCurrencySelector.swift */, + 2EB7758627FC67FD00269373 /* TransactionInputStore.swift */, + 2E6CF8DC27D78319004DCD7A /* CurrencySelectionStore.swift */, + 2EDA07A527EDE31100D6F09B /* Components */, + ); + path = TextFields; + sourceTree = ""; + }; 2E5C037F2738C55F008BFFD3 /* Onboarding */ = { isa = PBXGroup; children = ( @@ -578,6 +614,18 @@ path = Onboarding; sourceTree = ""; }; + 2EDA07A527EDE31100D6F09B /* Components */ = { + isa = PBXGroup; + children = ( + 2E35F99127B28E7600EB79CD /* SingleLineTextField.swift */, + 2EDA079F27EDE18C00D6F09B /* TextFieldInput.swift */, + 2EDA07A127EDE1AE00D6F09B /* TextFieldFooter.swift */, + 2E35F99927B3E99C00EB79CD /* TextFieldTitleAccessoryButtonStyle.swift */, + 2EB1C5E727D77F6100BC43D7 /* TextFieldStore.swift */, + ); + path = Components; + sourceTree = ""; + }; 660558F4270C85F7009D6954 /* Generated */ = { isa = PBXGroup; children = ( @@ -1078,12 +1126,15 @@ 660558F8270C862F009D6954 /* XCAssets+Generated.swift in Sources */, 0D35CC46277A36E00074316A /* ScrollableWhenScaled.swift in Sources */, F96B41E9273B501F0021B49A /* TransactionHistoryView.swift in Sources */, + 2EDA07A027EDE18C00D6F09B /* TextFieldInput.swift in Sources */, + 2EB7758727FC67FD00269373 /* TransactionInputStore.swift in Sources */, 669FDAE9272C23B3007B9422 /* CircularFrame.swift in Sources */, 9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */, 9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */, F96B41E8273B501F0021B49A /* TransactionDetailView.swift in Sources */, 663FABA2271D876C00E495F8 /* SecondaryButton.swift in Sources */, 0DC487C32772574C00BE6A63 /* ValidationSucceededView.swift in Sources */, + 2EB1C5E827D77F6100BC43D7 /* TextFieldStore.swift in Sources */, 0D8A43C4272AEEDE005A6414 /* SecantTextStyles.swift in Sources */, 0D1922F226BDE29300052649 /* ZcashSDKStubs.swift in Sources */, 9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */, @@ -1092,6 +1143,7 @@ 9E2DF99E27CF704D00649636 /* ImportWalletView.swift in Sources */, 0D535FE2271F9476009A9E3E /* EnumeratedChip.swift in Sources */, 6654C73E2715A41300901167 /* OnboardingStore.swift in Sources */, + 2E6CF8DD27D78319004DCD7A /* CurrencySelectionStore.swift in Sources */, 9EBEF87A27CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift in Sources */, 9E4DC6E227C4C6B700E657F4 /* SecantButtonStyles.swift in Sources */, 0DDB6A5127737D4A0012A410 /* ValidationFailedView.swift in Sources */, @@ -1100,6 +1152,7 @@ F9971A5327680DD000A2DB75 /* Profile.swift in Sources */, F93874F0273C4DE200F0E875 /* HomeStore.swift in Sources */, 669FDAEB272C23C2007B9422 /* CircularFrameBadge.swift in Sources */, + 2E8719CD27FB0D3B0082C926 /* TransactionCurrencySelector.swift in Sources */, F9971A6C27680E1000A2DB75 /* WalletInfoView.swift in Sources */, F9EEB8162742C2210032EEB8 /* WithStateBinding.swift in Sources */, F93673D62742CB840099C6AF /* Previews.swift in Sources */, @@ -1118,6 +1171,7 @@ 0DA13CA526C1963000E3B610 /* Balance.swift in Sources */, 2EA11F5B27467EF800709571 /* OnboardingFooterView.swift in Sources */, 66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */, + 2EDA07A427EDE2A900D6F09B /* DebugFrame.swift in Sources */, 0D3D040A2728B3A10032ABC1 /* RecoveryPhraseDisplayStore.swift in Sources */, 9E2AC10127D8EF0B0042AA47 /* MnemonicSeedPhraseProvider.swift in Sources */, 0D354A0926D5A9D000315F45 /* Services.swift in Sources */, @@ -1138,14 +1192,17 @@ 0D185819272723FF0046B928 /* ColoredChip.swift in Sources */, 2EA11F5D27467F7700709571 /* OnboardingContentView.swift in Sources */, 2E58E73B274679F000B2B84B /* OnboardingHeaderView.swift in Sources */, + 2E35F99227B28E7600EB79CD /* SingleLineTextField.swift in Sources */, 0D8A43C6272B129C005A6414 /* WordChipGrid.swift in Sources */, 66A0807B271993C500118B79 /* OnboardingProgressIndicator.swift in Sources */, 0D7DF08C271DCC0E00530046 /* ScreenBackground.swift in Sources */, F9C165C22740403600592F76 /* CreateView.swift in Sources */, F9C165B4274031F600592F76 /* Bindings.swift in Sources */, + 2E35F99A27B3E99C00EB79CD /* TextFieldTitleAccessoryButtonStyle.swift in Sources */, 9E2DF99C27CF704D00649636 /* ImportWalletStore.swift in Sources */, F9971A6627680DFE00A2DB75 /* SettingsView.swift in Sources */, F96B41EB273B50520021B49A /* Strings.swift in Sources */, + 2EDA07A227EDE1AE00D6F09B /* TextFieldFooter.swift in Sources */, F9971A5427680DD000A2DB75 /* ProfileView.swift in Sources */, F9971A6027680DF600A2DB75 /* Scan.swift in Sources */, 9EF8139127F191BF0075AF48 /* WalletStorageInteractor.swift in Sources */, @@ -1156,6 +1213,7 @@ F9971A6527680DFE00A2DB75 /* Settings.swift in Sources */, 9EF8139C27F47AED0075AF48 /* InitializationState.swift in Sources */, 0D0781C9278776D20083ACD7 /* ZcashSymbol.swift in Sources */, + 2E8719CB27FB09990082C926 /* TransactionTextField.swift in Sources */, 6654C7412715A47300901167 /* Onboarding.swift in Sources */, F9C165C42740403600592F76 /* SentView.swift in Sources */, F9971A5927680DDE00A2DB75 /* Request.swift in Sources */, diff --git a/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7289df1..4e8bcfd 100644 --- a/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -69,8 +69,8 @@ "repositoryURL": "https://github.com/apple/swift-crypto.git", "state": { "branch": null, - "revision": "a8911e0fadc25aef1071d582355bd1037a176060", - "version": "2.0.4" + "revision": "067254c79435de759aeef4a6a03e43d087d61312", + "version": "2.0.5" } }, { diff --git a/secant/Assets.xcassets/Icons/qr-code.imageset/Contents.json b/secant/Assets.xcassets/Icons/qr-code.imageset/Contents.json new file mode 100644 index 0000000..d3a392a --- /dev/null +++ b/secant/Assets.xcassets/Icons/qr-code.imageset/Contents.json @@ -0,0 +1,56 @@ +{ + "images" : [ + { + "filename" : "qr_lighttheme.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "qr_darktheme.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "qr_lighttheme@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "qr_darktheme@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "qr_lighttheme@3x.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "qr_darktheme@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/secant/Assets.xcassets/Icons/qr-code.imageset/qr_darktheme.png b/secant/Assets.xcassets/Icons/qr-code.imageset/qr_darktheme.png new file mode 100644 index 0000000..341a34e Binary files /dev/null and b/secant/Assets.xcassets/Icons/qr-code.imageset/qr_darktheme.png differ diff --git a/secant/Assets.xcassets/Icons/qr-code.imageset/qr_darktheme@2x.png b/secant/Assets.xcassets/Icons/qr-code.imageset/qr_darktheme@2x.png new file mode 100644 index 0000000..a26f8fa Binary files /dev/null and b/secant/Assets.xcassets/Icons/qr-code.imageset/qr_darktheme@2x.png differ diff --git a/secant/Assets.xcassets/Icons/qr-code.imageset/qr_darktheme@3x.png b/secant/Assets.xcassets/Icons/qr-code.imageset/qr_darktheme@3x.png new file mode 100644 index 0000000..7446400 Binary files /dev/null and b/secant/Assets.xcassets/Icons/qr-code.imageset/qr_darktheme@3x.png differ diff --git a/secant/Assets.xcassets/Icons/qr-code.imageset/qr_lighttheme.png b/secant/Assets.xcassets/Icons/qr-code.imageset/qr_lighttheme.png new file mode 100644 index 0000000..263e065 Binary files /dev/null and b/secant/Assets.xcassets/Icons/qr-code.imageset/qr_lighttheme.png differ diff --git a/secant/Assets.xcassets/Icons/qr-code.imageset/qr_lighttheme@2x.png b/secant/Assets.xcassets/Icons/qr-code.imageset/qr_lighttheme@2x.png new file mode 100644 index 0000000..0cefc87 Binary files /dev/null and b/secant/Assets.xcassets/Icons/qr-code.imageset/qr_lighttheme@2x.png differ diff --git a/secant/Assets.xcassets/Icons/qr-code.imageset/qr_lighttheme@3x.png b/secant/Assets.xcassets/Icons/qr-code.imageset/qr_lighttheme@3x.png new file mode 100644 index 0000000..bff5f46 Binary files /dev/null and b/secant/Assets.xcassets/Icons/qr-code.imageset/qr_lighttheme@3x.png differ diff --git a/secant/Assets.xcassets/Icons/swap.imageset/Contents.json b/secant/Assets.xcassets/Icons/swap.imageset/Contents.json new file mode 100644 index 0000000..f80fa42 --- /dev/null +++ b/secant/Assets.xcassets/Icons/swap.imageset/Contents.json @@ -0,0 +1,56 @@ +{ + "images" : [ + { + "filename" : "swapicon_lighttheme.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "swapicon_darktheme.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "swapicon_lighttheme@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "swapicon_darktheme@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "swapicon_lighttheme@3x.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "swapicon_darktheme@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/secant/Assets.xcassets/Icons/swap.imageset/swapicon_darktheme.png b/secant/Assets.xcassets/Icons/swap.imageset/swapicon_darktheme.png new file mode 100644 index 0000000..51a85b8 Binary files /dev/null and b/secant/Assets.xcassets/Icons/swap.imageset/swapicon_darktheme.png differ diff --git a/secant/Assets.xcassets/Icons/swap.imageset/swapicon_darktheme@2x.png b/secant/Assets.xcassets/Icons/swap.imageset/swapicon_darktheme@2x.png new file mode 100644 index 0000000..ba09724 Binary files /dev/null and b/secant/Assets.xcassets/Icons/swap.imageset/swapicon_darktheme@2x.png differ diff --git a/secant/Assets.xcassets/Icons/swap.imageset/swapicon_darktheme@3x.png b/secant/Assets.xcassets/Icons/swap.imageset/swapicon_darktheme@3x.png new file mode 100644 index 0000000..5de5287 Binary files /dev/null and b/secant/Assets.xcassets/Icons/swap.imageset/swapicon_darktheme@3x.png differ diff --git a/secant/Assets.xcassets/Icons/swap.imageset/swapicon_lighttheme.png b/secant/Assets.xcassets/Icons/swap.imageset/swapicon_lighttheme.png new file mode 100644 index 0000000..0601780 Binary files /dev/null and b/secant/Assets.xcassets/Icons/swap.imageset/swapicon_lighttheme.png differ diff --git a/secant/Assets.xcassets/Icons/swap.imageset/swapicon_lighttheme@2x.png b/secant/Assets.xcassets/Icons/swap.imageset/swapicon_lighttheme@2x.png new file mode 100644 index 0000000..d1aa415 Binary files /dev/null and b/secant/Assets.xcassets/Icons/swap.imageset/swapicon_lighttheme@2x.png differ diff --git a/secant/Assets.xcassets/Icons/swap.imageset/swapicon_lighttheme@3x.png b/secant/Assets.xcassets/Icons/swap.imageset/swapicon_lighttheme@3x.png new file mode 100644 index 0000000..1d489f8 Binary files /dev/null and b/secant/Assets.xcassets/Icons/swap.imageset/swapicon_lighttheme@3x.png differ diff --git a/secant/Colors.xcassets/Cursor/Bar.colorset/Contents.json b/secant/Colors.xcassets/Cursor/Bar.colorset/Contents.json new file mode 100644 index 0000000..970dc26 --- /dev/null +++ b/secant/Colors.xcassets/Cursor/Bar.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.171", + "green" : "0.138", + "red" : "0.126" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.810", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/secant/Colors.xcassets/Cursor/Contents.json b/secant/Colors.xcassets/Cursor/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/secant/Colors.xcassets/Cursor/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/secant/Colors.xcassets/TextField/Contents.json b/secant/Colors.xcassets/TextField/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/secant/Colors.xcassets/TextField/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/secant/Colors.xcassets/TextField/TitleAccessoryButton.colorset/Contents.json b/secant/Colors.xcassets/TextField/TitleAccessoryButton.colorset/Contents.json new file mode 100644 index 0000000..8219f80 --- /dev/null +++ b/secant/Colors.xcassets/TextField/TitleAccessoryButton.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.110", + "green" : "0.110", + "red" : "0.110" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.110", + "green" : "0.110", + "red" : "0.110" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/secant/Colors.xcassets/TextField/TitleAccessoryButtonPressed.colorset/Contents.json b/secant/Colors.xcassets/TextField/TitleAccessoryButtonPressed.colorset/Contents.json new file mode 100644 index 0000000..a568890 --- /dev/null +++ b/secant/Colors.xcassets/TextField/TitleAccessoryButtonPressed.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.226", + "green" : "0.226", + "red" : "0.226" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.226", + "green" : "0.226", + "red" : "0.226" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/secant/Colors.xcassets/TextField/Underline/Contents.json b/secant/Colors.xcassets/TextField/Underline/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/secant/Colors.xcassets/TextField/Underline/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/secant/Colors.xcassets/TextField/Underline/Gray.colorset/Contents.json b/secant/Colors.xcassets/TextField/Underline/Gray.colorset/Contents.json new file mode 100644 index 0000000..9751b83 --- /dev/null +++ b/secant/Colors.xcassets/TextField/Underline/Gray.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.827", + "blue" : "0.623", + "green" : "0.589", + "red" : "0.560" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.290", + "green" : "0.275", + "red" : "0.259" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/secant/Colors.xcassets/TextField/Underline/Purple.colorset/Contents.json b/secant/Colors.xcassets/TextField/Underline/Purple.colorset/Contents.json new file mode 100644 index 0000000..323fc68 --- /dev/null +++ b/secant/Colors.xcassets/TextField/Underline/Purple.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.678", + "green" : "0.459", + "red" : "0.557" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.678", + "green" : "0.459", + "red" : "0.557" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/secant/Features/Home/Views/HomeView.swift b/secant/Features/Home/Views/HomeView.swift index 520a7a2..1f12cdd 100644 --- a/secant/Features/Home/Views/HomeView.swift +++ b/secant/Features/Home/Views/HomeView.swift @@ -25,7 +25,7 @@ struct HomeView: View { reducer: SendReducer.default( whenDone: { HomeViewStore(store).send(.updateRoute(nil)) } ) - .debug(), + .debug(), environment: () ) ) diff --git a/secant/Generated/XCAssets+Generated.swift b/secant/Generated/XCAssets+Generated.swift index 21942a0..de418f8 100644 --- a/secant/Generated/XCAssets+Generated.swift +++ b/secant/Generated/XCAssets+Generated.swift @@ -37,7 +37,9 @@ internal enum Asset { internal static let bank = ImageAsset(name: "bank") internal static let list = ImageAsset(name: "list") internal static let profile = ImageAsset(name: "profile") + internal static let qrCode = ImageAsset(name: "qr-code") internal static let shield = ImageAsset(name: "shield") + internal static let swap = ImageAsset(name: "swap") } } internal enum Colors { @@ -61,6 +63,9 @@ internal enum Asset { internal static let secondaryButton = ColorAsset(name: "SecondaryButton") internal static let secondaryButtonPressed = ColorAsset(name: "SecondaryButtonPressed") } + internal enum Cursor { + internal static let bar = ColorAsset(name: "Bar") + } internal enum Onboarding { internal static let badgeShadow = ColorAsset(name: "BadgeShadow") internal static let circularFrameDarkOutlineGradientEnd = ColorAsset(name: "CircularFrameDarkOutlineGradientEnd") @@ -104,6 +109,14 @@ internal enum Asset { internal static let captionTextShadow = ColorAsset(name: "captionTextShadow") internal static let highlightedSuperscriptText = ColorAsset(name: "highlightedSuperscriptText") } + internal enum TextField { + internal static let titleAccessoryButton = ColorAsset(name: "TitleAccessoryButton") + internal static let titleAccessoryButtonPressed = ColorAsset(name: "TitleAccessoryButtonPressed") + internal enum Underline { + internal static let gray = ColorAsset(name: "Gray") + internal static let purple = ColorAsset(name: "Purple") + } + } internal enum ZcashBadge { internal static let zcashLogoFill = ColorAsset(name: "ZcashLogoFill") internal static let innerCircle = ColorAsset(name: "innerCircle") diff --git a/secant/UIComponents/TextFields/Components/SingleLineTextField.swift b/secant/UIComponents/TextFields/Components/SingleLineTextField.swift new file mode 100644 index 0000000..af25276 --- /dev/null +++ b/secant/UIComponents/TextFields/Components/SingleLineTextField.swift @@ -0,0 +1,124 @@ +// +// SingleLineTextField.swift +// secant-testnet +// +// Created by Adam Stener on 2/8/22. +// + +import SwiftUI +import ComposableArchitecture + +struct SingleLineTextField: View + where TitleAccessoryContent: View, InputAccessoryContent: View { + let placeholderText: String + let title: String + let store: TextFieldStore + + @ViewBuilder let titleAccessoryView: TitleAccessoryContent + @ViewBuilder let inputAccessoryView: InputAccessoryContent + + var body: some View { + VStack(spacing: 0) { + HStack { + Text(title) + .lineLimit(1) + .truncationMode(.middle) + + Spacer() + + titleAccessoryView + } + + HStack { + TextFieldInput( + placeholder: placeholderText, + store: store + ) + + Spacer() + + inputAccessoryView + } + .frame(maxHeight: 50) + + TextFieldFooter() + } + } +} + +struct SingleLineTextField_Previews: PreviewProvider { + struct TransactionPreview: View { + let store: TextFieldStore + let maxTransactionValue = 500.0 + + var body: some View { + WithViewStore(store) { viewStore in + VStack { + SingleLineTextField( + placeholderText: "$0", + title: "How much?", + store: store, + titleAccessoryView: { + Button( + action: { + viewStore.send(.set("\(500)")) + }, + label: { Text("Max") } + ) + .textFieldTitleAccessoryButtonStyle + }, + inputAccessoryView: { EmptyView() } + ) + } + } + } + } + + struct UserPreview: View { + let store: TextFieldStore + + var body: some View { + VStack { + SingleLineTextField( + placeholderText: "", + title: "Who would you like to deal with really long text today?", + store: store, + titleAccessoryView: { EmptyView() }, + inputAccessoryView: { EmptyView() } + ) + } + } + } + + static var previews: some View { + TransactionPreview( + store: TextFieldStore( + initialState: .init( + validationType: .floatingPoint, + text: "" + ), + reducer: .default, + environment: .init() + ) + ) + .preferredColorScheme(.dark) + .padding(.horizontal, 50) + .applyScreenBackground() + .previewLayout(.fixed(width: 500, height: 200)) + + UserPreview( + store: TextFieldStore( + initialState: .init( + validationType: .email, + text: "" + ), + reducer: .default, + environment: .init() + ) + ) + .preferredColorScheme(.light) + .padding(.horizontal, 50) + .applyScreenBackground() + .previewLayout(.fixed(width: 500, height: 200)) + } +} diff --git a/secant/UIComponents/TextFields/Components/TextFieldFooter.swift b/secant/UIComponents/TextFields/Components/TextFieldFooter.swift new file mode 100644 index 0000000..afd8e7b --- /dev/null +++ b/secant/UIComponents/TextFields/Components/TextFieldFooter.swift @@ -0,0 +1,23 @@ +// +// TextFieldFooter.swift +// secant-testnet +// +// Created by Adam Stener on 3/25/22. +// + +import SwiftUI +import ComposableArchitecture + +struct TextFieldFooter: View { + var body: some View { + HStack(spacing: 0) { + Rectangle() + .frame(height: 1.5) + .foregroundColor(Asset.Colors.TextField.Underline.purple.color) + + Rectangle() + .frame(height: 1.5) + .foregroundColor(Asset.Colors.TextField.Underline.gray.color) + } + } +} diff --git a/secant/UIComponents/TextFields/Components/TextFieldInput.swift b/secant/UIComponents/TextFields/Components/TextFieldInput.swift new file mode 100644 index 0000000..e6934f2 --- /dev/null +++ b/secant/UIComponents/TextFields/Components/TextFieldInput.swift @@ -0,0 +1,29 @@ +// +// TextFieldInput.swift +// secant-testnet +// +// Created by Adam Stener on 3/25/22. +// + +import SwiftUI +import ComposableArchitecture + +struct TextFieldInput: View { + let placeholder: String + let store: TextFieldStore + + var body: some View { + WithViewStore(store) { viewStore in + TextField( + placeholder, + text: Binding( + get: { viewStore.state.text }, + set: { viewStore.send(.set($0)) } + ) + ) + .lineLimit(1) + .truncationMode(.middle) + .accentColor(Asset.Colors.Cursor.bar.color) + } + } +} diff --git a/secant/UIComponents/TextFields/Components/TextFieldStore.swift b/secant/UIComponents/TextFields/Components/TextFieldStore.swift new file mode 100644 index 0000000..00e61f6 --- /dev/null +++ b/secant/UIComponents/TextFields/Components/TextFieldStore.swift @@ -0,0 +1,62 @@ +// +// TextFieldStore.swift +// secant-testnet +// +// Created by Adam Stener on 3/8/22. +// + +import ComposableArchitecture + +typealias TextFieldReducer = Reducer +typealias TextFieldStore = Store + +struct TextFieldState: Equatable { + var validationType: String.ValidationType? + var text: String + var valid = false + + init(validationType: String.ValidationType?, text: String) { + self.validationType = validationType + self.text = text + } +} + +enum TextFieldAction { + case apply((String) -> String) + case set(String) +} + +struct TextFieldEnvironment: Equatable { } + +extension TextFieldReducer { + static let `default` = TextFieldReducer { state, action, _ in + switch action { + case .apply(let action): + state.text = action(state.text) + state.valid = state.text.isValid(for: state.validationType) + + case .set(let text): + state.text = text + state.valid = state.text.isValid(for: state.validationType) + } + return .none + } +} + +extension TextFieldStore { + static var transaction: Self { + .init( + initialState: .init(validationType: .floatingPoint, text: ""), + reducer: .default, + environment: .init() + ) + } + + static var address: Self { + .init( + initialState: .init(validationType: .email, text: ""), + reducer: .default, + environment: .init() + ) + } +} diff --git a/secant/UIComponents/TextFields/Components/TextFieldTitleAccessoryButtonStyle.swift b/secant/UIComponents/TextFields/Components/TextFieldTitleAccessoryButtonStyle.swift new file mode 100644 index 0000000..50dd045 --- /dev/null +++ b/secant/UIComponents/TextFields/Components/TextFieldTitleAccessoryButtonStyle.swift @@ -0,0 +1,29 @@ +// +// TextFieldTitleAccessoryButtonStyle.swift +// secant-testnet +// +// Created by Adam Stener on 2/9/22. +// + +import SwiftUI + +struct TextFieldTitleAccessoryButtonStyle: ButtonStyle { + func makeBody(configuration: Configuration) -> some View { + configuration.label + .padding(.horizontal, 10) + .padding(.vertical, 3) + .foregroundColor(.white) + .background( + configuration.isPressed ? + Asset.Colors.TextField.titleAccessoryButtonPressed.color : + Asset.Colors.TextField.titleAccessoryButton.color + ) + .cornerRadius(6) + } +} + +extension Button { + var textFieldTitleAccessoryButtonStyle: some View { + buttonStyle(TextFieldTitleAccessoryButtonStyle()) + } +} diff --git a/secant/UIComponents/TextFields/CurrencySelectionStore.swift b/secant/UIComponents/TextFields/CurrencySelectionStore.swift new file mode 100644 index 0000000..f1b3c76 --- /dev/null +++ b/secant/UIComponents/TextFields/CurrencySelectionStore.swift @@ -0,0 +1,53 @@ +// +// ExchangeStore.swift +// secant-testnet +// +// Created by Adam Stener on 3/8/22. +// + +import ComposableArchitecture + +typealias CurrencySelectionReducer = Reducer< + CurrencySelectionState, + CurrencySelectionAction, + CurrencySelectionEnvironment +> + +typealias CurrencySelectionStore = Store + +struct CurrencySelectionState: Equatable { + enum Currency: Equatable { + case usd + case zec + + var acronym: String { + switch self { + case .usd: return "USD" + case .zec: return "ZEC" + } + } + } + + var currencyType: Currency = .zec +} + +enum CurrencySelectionAction: Equatable { + case swapCurrencyType +} + +struct CurrencySelectionEnvironment: Equatable { } + +extension CurrencySelectionReducer { + static var `default`: Self { + .init { state, action, _ in + switch action { + case .swapCurrencyType: + switch state.currencyType { + case .usd: state.currencyType = .zec + case .zec: state.currencyType = .usd + } + } + return .none + } + } +} diff --git a/secant/UIComponents/TextFields/TransactionCurrencySelector.swift b/secant/UIComponents/TextFields/TransactionCurrencySelector.swift new file mode 100644 index 0000000..229ab4e --- /dev/null +++ b/secant/UIComponents/TextFields/TransactionCurrencySelector.swift @@ -0,0 +1,36 @@ +// +// TransactionCurrencySelector.swift +// secant-testnet +// +// Created by Adam Stener on 4/4/22. +// + +import SwiftUI +import ComposableArchitecture + +struct TransactionCurrencySelector: View { + let store: CurrencySelectionStore + + var body: some View { + WithViewStore(store) { viewStore in + Button( + action: { viewStore.send(.swapCurrencyType) }, + label: { + HStack { + Text(CurrencySelectionState.Currency.usd.acronym) + .foregroundColor( + viewStore.currencyType == .usd ? .yellow : .white + ) + + Asset.Assets.Icons.swap.image + + Text(CurrencySelectionState.Currency.zec.acronym) + .foregroundColor( + viewStore.currencyType == .zec ? .yellow : .white + ) + } + } + ) + } + } +} diff --git a/secant/UIComponents/TextFields/TransactionInputStore.swift b/secant/UIComponents/TextFields/TransactionInputStore.swift new file mode 100644 index 0000000..30f64d9 --- /dev/null +++ b/secant/UIComponents/TextFields/TransactionInputStore.swift @@ -0,0 +1,101 @@ +// +// 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 + +struct TransactionInputState: Equatable { + var textFieldState: TextFieldState + var currencySelectionState: CurrencySelectionState +} + +enum TransactionInputAction { + case setMax(Double) + case textField(TextFieldAction) + case currencySelection(CurrencySelectionAction) +} + +struct TransactionInputEnvironment: Equatable {} + +func maxOverride(_ reducer: @escaping (TransactionReducerData)) -> TransactionReducerData { + return { state, action in + switch action { + case .setMax(let value): + state.textFieldState.text = "\(value)" + state.currencySelectionState.currencyType = .usd + + default: break + } + + reducer(&state, action) + } +} + +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)" + + 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() } + ) +} diff --git a/secant/UIComponents/TextFields/TransactionTextField.swift b/secant/UIComponents/TextFields/TransactionTextField.swift new file mode 100644 index 0000000..0497357 --- /dev/null +++ b/secant/UIComponents/TextFields/TransactionTextField.swift @@ -0,0 +1,72 @@ +// +// TransactionTextField.swift +// secant-testnet +// +// Created by Adam Stener on 4/4/22. +// + +import SwiftUI +import ComposableArchitecture + +struct TransactionTextField: View { + let store: TransactionInputStore + + // 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 = 500.0 + + var body: some View { + WithViewStore(store) { viewStore in + VStack { + SingleLineTextField( + placeholderText: "$0", + title: "How much?", + store: store.scope( + state: \.textFieldState, + action: TransactionInputAction.textField + ), + titleAccessoryView: { + Button( + action: { + viewStore.send(.setMax(maxTransactionValue)) + }, + label: { Text("Max") } + ) + .textFieldTitleAccessoryButtonStyle + }, + inputAccessoryView: { + TransactionCurrencySelector( + store: store.scope( + state: \.currencySelectionState, + action: TransactionInputAction.currencySelection + ) + ) + } + ) + } + } + } +} + +struct TransactionTextField_Previews: PreviewProvider { + static var previews: some View { + TransactionTextField( + store: TransactionInputStore( + initialState: .init( + textFieldState: .init( + validationType: .floatingPoint, + text: "" + ), + currencySelectionState: .init(currencyType: .usd) + ), + reducer: .default, + environment: .init() + ) + ) + .preferredColorScheme(.dark) + .padding(.horizontal, 50) + .applyScreenBackground() + .previewLayout(.fixed(width: 500, height: 200)) + } +} diff --git a/secant/Util/DebugFrame.swift b/secant/Util/DebugFrame.swift new file mode 100644 index 0000000..fc6d95f --- /dev/null +++ b/secant/Util/DebugFrame.swift @@ -0,0 +1,31 @@ +// +// DebugFrame.swift +// secant-testnet +// +// Created by Adam Stener on 3/25/22. +// + +import SwiftUI + +struct DebugFrame: ViewModifier { + let color: Color + func body(content: Content) -> some View { + content + .overlay( + Rectangle() + .strokeBorder( + style: StrokeStyle( + lineWidth: 0.5, + dash: [3] + ) + ) + ) + .foregroundColor(color) + } +} + +extension View { + func debug(_ color: Color) -> some View { + modifier(DebugFrame(color: color)) + } +} diff --git a/secant/Util/Strings.swift b/secant/Util/Strings.swift index 18231dc..1ff9e03 100644 --- a/secant/Util/Strings.swift +++ b/secant/Util/Strings.swift @@ -8,3 +8,57 @@ extension String { self.init(stringLiteral: output) } } + +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 phoneRegex = "^^\\+(?:[0-9]?){6,14}[0-9]$" + + public enum ValidationType: Equatable { + case custom(String) + case email + case floatingPoint + case maxLength(Int) + case minLength(Int) + case phoneNumber + + func isValid(text: String) -> Bool { + switch self { + case .custom(let regex): + return text.validate(using: regex) + + case .email: + return text.validate(using: .emailRegex) + + case .floatingPoint: + return text.validate(using: .floatingPointRegex) + + case .maxLength(let length): + return text.count <= length && !text.isEmpty + + case .minLength(let length): + return text.count >= length + + case .phoneNumber: + return text.validate(using: .phoneRegex) + } + } + } + + private func validate(using regex: String) -> Bool { + guard let regex = try? NSRegularExpression(pattern: regex) else { return false } + + return regex.firstMatch( + in: self, + options: [], + range: NSRange(location: 0, length: self.utf16.count) + ) != nil + } + + public func isValid(for validationType: ValidationType?) -> Bool { + guard let validationType = validationType else { return true } + + return validationType.isValid(text: self) + } +}