diff --git a/AlphaWallet.xcodeproj/project.pbxproj b/AlphaWallet.xcodeproj/project.pbxproj index a252911b46..2dc49c6ddd 100644 --- a/AlphaWallet.xcodeproj/project.pbxproj +++ b/AlphaWallet.xcodeproj/project.pbxproj @@ -11,6 +11,14 @@ 02220CFE273A9CDB006A09BF /* SaveCustomRpcViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02220CFD273A9CDA006A09BF /* SaveCustomRpcViewController.swift */; }; 02220D00273A9DD6006A09BF /* SaveCustomRpcViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02220CFF273A9DD6006A09BF /* SaveCustomRpcViewModel.swift */; }; 02220D02273A9E24006A09BF /* SaveCustomRpcView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02220D01273A9E23006A09BF /* SaveCustomRpcView.swift */; }; + 025F5D162760C73300B2A3BC /* ExportJsonKeystorePasswordViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025F5D152760C73300B2A3BC /* ExportJsonKeystorePasswordViewModel.swift */; }; + 025F5D182760C74400B2A3BC /* ExportJsonKeystoreCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025F5D172760C74400B2A3BC /* ExportJsonKeystoreCoordinator.swift */; }; + 025F5D1B2760C75800B2A3BC /* ExportJsonKeystorePasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025F5D192760C75800B2A3BC /* ExportJsonKeystorePasswordViewController.swift */; }; + 025F5D1C2760C75800B2A3BC /* ExportJsonKeystoreFileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025F5D1A2760C75800B2A3BC /* ExportJsonKeystoreFileViewController.swift */; }; + 025F5D1F2760C76A00B2A3BC /* ExportJsonKeystoreFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025F5D1D2760C76A00B2A3BC /* ExportJsonKeystoreFileView.swift */; }; + 025F5D202760C76A00B2A3BC /* ExportJsonKeystorePasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025F5D1E2760C76A00B2A3BC /* ExportJsonKeystorePasswordView.swift */; }; + 025F5D2F2760CDE600B2A3BC /* StringValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025F5D2E2760CDE600B2A3BC /* StringValidator.swift */; }; + 025F5D3D2760CE9E00B2A3BC /* UIFactoryColors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 025F5D3C2760CE9E00B2A3BC /* UIFactoryColors.xcassets */; }; 0DF46B4002E13613F768E7AE /* Pods_AlphaWalletTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E36AB2DC7832A980726A4AB1 /* Pods_AlphaWalletTests.framework */; }; 290B2B541F8F50030053C83E /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 290B2B561F8F50030053C83E /* Localizable.strings */; }; 290B2B5F1F9177860053C83E /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 290B2B5E1F9177860053C83E /* UIImage.swift */; }; @@ -1037,6 +1045,14 @@ 02220CFD273A9CDA006A09BF /* SaveCustomRpcViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveCustomRpcViewController.swift; sourceTree = ""; }; 02220CFF273A9DD6006A09BF /* SaveCustomRpcViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveCustomRpcViewModel.swift; sourceTree = ""; }; 02220D01273A9E23006A09BF /* SaveCustomRpcView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SaveCustomRpcView.swift; sourceTree = ""; }; + 025F5D152760C73300B2A3BC /* ExportJsonKeystorePasswordViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportJsonKeystorePasswordViewModel.swift; sourceTree = ""; }; + 025F5D172760C74400B2A3BC /* ExportJsonKeystoreCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportJsonKeystoreCoordinator.swift; sourceTree = ""; }; + 025F5D192760C75800B2A3BC /* ExportJsonKeystorePasswordViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportJsonKeystorePasswordViewController.swift; sourceTree = ""; }; + 025F5D1A2760C75800B2A3BC /* ExportJsonKeystoreFileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportJsonKeystoreFileViewController.swift; sourceTree = ""; }; + 025F5D1D2760C76A00B2A3BC /* ExportJsonKeystoreFileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportJsonKeystoreFileView.swift; sourceTree = ""; }; + 025F5D1E2760C76A00B2A3BC /* ExportJsonKeystorePasswordView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportJsonKeystorePasswordView.swift; sourceTree = ""; }; + 025F5D2E2760CDE600B2A3BC /* StringValidator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringValidator.swift; sourceTree = ""; }; + 025F5D3C2760CE9E00B2A3BC /* UIFactoryColors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = UIFactoryColors.xcassets; sourceTree = ""; }; 0B5A03D1E5E417FE79607D0F /* Pods_AlphaWallet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_AlphaWallet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 120A2B8C48180E93DBCCC2EE /* Pods-AlphaWalletUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AlphaWalletUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AlphaWalletUITests/Pods-AlphaWalletUITests.release.xcconfig"; sourceTree = ""; }; 290B2B551F8F50030053C83E /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; @@ -2067,6 +2083,14 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 025F5D3E2760CEA800B2A3BC /* UIFactory */ = { + isa = PBXGroup; + children = ( + 025F5D3C2760CE9E00B2A3BC /* UIFactoryColors.xcassets */, + ); + path = UIFactory; + sourceTree = ""; + }; 290B2B511F8F4F840053C83E /* Localization */ = { isa = PBXGroup; children = ( @@ -2489,6 +2513,7 @@ 5E7C72571AB0FECB26FEB1B1 /* ClearDappBrowserCacheCoordinator.swift */, 02220CFB273A9A02006A09BF /* SaveCustomRpcCoordinator.swift */, 5E7C757AEC6586F51EC75646 /* PingInfuraCoordinator.swift */, + 025F5D172760C74400B2A3BC /* ExportJsonKeystoreCoordinator.swift */, ); path = Coordinators; sourceTree = ""; @@ -2761,6 +2786,7 @@ 87ED8FAE2488EA9C0005C69B /* SupportViewModel.swift */, 02220CFF273A9DD6006A09BF /* SaveCustomRpcViewModel.swift */, 5E7C73BF5CE15E6D7AFC3F0C /* ChooseSendPrivateTransactionsProviderViewModel.swift */, + 025F5D152760C73300B2A3BC /* ExportJsonKeystorePasswordViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -3241,6 +3267,8 @@ 87509A6126F8D67E00D3EE85 /* CollectUsersEmailViewController.swift */, 02220CFD273A9CDA006A09BF /* SaveCustomRpcViewController.swift */, 5E7C7177F1DDDDDDE020CB4D /* ChooseSendPrivateTransactionsProviderViewController.swift */, + 025F5D1A2760C75800B2A3BC /* ExportJsonKeystoreFileViewController.swift */, + 025F5D192760C75800B2A3BC /* ExportJsonKeystorePasswordViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -3945,6 +3973,8 @@ 876C80CC2673940B00B16595 /* SwitchView.swift */, 02220D01273A9E23006A09BF /* SaveCustomRpcView.swift */, 5E7C73BFE30E0D43E97806EF /* SelectionTableViewCell.swift */, + 025F5D1D2760C76A00B2A3BC /* ExportJsonKeystoreFileView.swift */, + 025F5D1E2760C76A00B2A3BC /* ExportJsonKeystorePasswordView.swift */, ); path = Views; sourceTree = ""; @@ -4870,6 +4900,7 @@ CCA4FE341FD427CA00749AE4 /* Helpers */ = { isa = PBXGroup; children = ( + 025F5D3E2760CEA800B2A3BC /* UIFactory */, CCA4FE351FD4282400749AE4 /* DeviceChecker.swift */, CCA4FE371FD428B300749AE4 /* JailbreakChecker.swift */, 5E7C77E1E6194F5A1DC8D645 /* ScreenChecker.swift */, @@ -4881,6 +4912,7 @@ 5E7C7B80946D854599838AD1 /* RemoteLogger.swift */, 5E7C77DBECDF831CD6C8FF71 /* Environment.swift */, 5F4D80A326A3F97B00BB1135 /* DeviceModel.swift */, + 025F5D2E2760CDE600B2A3BC /* StringValidator.swift */, ); path = Helpers; sourceTree = ""; @@ -5080,6 +5112,7 @@ C887C53A2057B703005ACF81 /* termsOfService.html in Resources */, C876FF7D204A79D300B7D0EA /* SourceSansPro-Bold.otf in Resources */, C880330D2054371500D73D6F /* non_asset_catalog_redemption_location@2x.png in Resources */, + 025F5D3D2760CE9E00B2A3BC /* UIFactoryColors.xcassets in Resources */, 771AA966200D5F1900D25403 /* WordCollectionViewCell.xib in Resources */, 87B93B112726C59D00F6EA73 /* BrowserStorageSubscription.js in Resources */, 87B93B102726C59D00F6EA73 /* config.js in Resources */, @@ -5471,6 +5504,7 @@ 2996F14D1F6CA743005C33AE /* UIViewController.swift in Sources */, 2959961F1FAE759700DB66A8 /* RawTransaction.swift in Sources */, 87E2555824F52EBF00F025F7 /* GasSpeedTableViewCellViewModel.swift in Sources */, + 025F5D202760C76A00B2A3BC /* ExportJsonKeystorePasswordView.swift in Sources */, 295B61D41FE7D5B500642E60 /* CurrencyFormatter.swift in Sources */, 296421991F70C1F900EB363B /* EmptyView.swift in Sources */, 771AA962200D5EC700D25403 /* PassphraseViewModel.swift in Sources */, @@ -5612,6 +5646,7 @@ 87BBF9972563DD7600FF4846 /* TransactionInProgressCoordinatorBridgeToPromise.swift in Sources */, 291F52A91F6B7BE100B369AB /* BlockNumber.swift in Sources */, 739533971FEFF5FD0084AFAB /* Currency.swift in Sources */, + 025F5D1B2760C75800B2A3BC /* ExportJsonKeystorePasswordViewController.swift in Sources */, 298542F31FBD594D00CB5081 /* SettingsViewModel.swift in Sources */, 874D099025EE32EF00A58EF2 /* SignatureConfirmationDetailsViewModel.swift in Sources */, CCA4FE381FD428B300749AE4 /* JailbreakChecker.swift in Sources */, @@ -5798,6 +5833,7 @@ 87D175EB24AEF8B5002130D2 /* UITableView.swift in Sources */, 76F1D7F08263A663C3A67926 /* GetIsERC721ContractCoordinator.swift in Sources */, 87BBF9872563DD7600FF4846 /* WalletConnectSessionDetailsViewModel.swift in Sources */, + 025F5D162760C73300B2A3BC /* ExportJsonKeystorePasswordViewModel.swift in Sources */, 5E7C79DE8864702C51C0A7CC /* ResultResult.swift in Sources */, 87CDD49025F100AE0009B6AC /* Array.swift in Sources */, 87574A3025E7D41E00CAA0BB /* AnalyticsService.swift in Sources */, @@ -5893,6 +5929,7 @@ 76F1D5ECC391A932C96CAC13 /* GetENSOwnerCoordinator.swift in Sources */, 76F1D9BBB4ACAA00C8391172 /* GetENSInfoEncode.swift in Sources */, 8703F66726135B330082EE25 /* ChartHistory.swift in Sources */, + 025F5D2F2760CDE600B2A3BC /* StringValidator.swift in Sources */, 76F1D5B10569F2351CA98A93 /* GasLimitConfiguration.swift in Sources */, 874DED1924C1BD2C006C8FCE /* SelectTokenViewModel.swift in Sources */, 5E7C7EB80D32A2F366E79140 /* SettingsHeaderView.swift in Sources */, @@ -6033,6 +6070,7 @@ 5E7C7777BDB574E021D2D6F1 /* LegacyFileBasedKeystore.swift in Sources */, 87713EB0264BAB2500B1B9CB /* TokenPagesContainerView.swift in Sources */, 5E7C73F6762376F5B2B4214D /* EtherKeystore.swift in Sources */, + 025F5D1F2760C76A00B2A3BC /* ExportJsonKeystoreFileView.swift in Sources */, 5E7C7A9363A9DF11B233F9DC /* Keystore.swift in Sources */, 5E7C7B14B0D172F91D52F0BF /* EthereumSigner.swift in Sources */, 879F185926E7412D000602F2 /* Erc1155TokenInstanceViewModel.swift in Sources */, @@ -6133,7 +6171,9 @@ 5E7C7C5C350376B83D23154F /* MixpanelCoordinator.swift in Sources */, 5E7C769C85D1EBB44DEA5AC5 /* AnalyticsEventPropertyValue.swift in Sources */, 5E7C71EFB766F41D1BA5E1FB /* AnalyticsTypes.swift in Sources */, + 025F5D1C2760C75800B2A3BC /* ExportJsonKeystoreFileViewController.swift in Sources */, 5E7C760168C99F9D50800DFD /* AnalyticsCoordinator.swift in Sources */, + 025F5D182760C74400B2A3BC /* ExportJsonKeystoreCoordinator.swift in Sources */, 5E7C79BFFFB6A5FE833489C0 /* ActivitiesCoordinator.swift in Sources */, 5E7C783C2C5F4CFFC43AD63B /* EventSourceCoordinatorForActivities.swift in Sources */, 5E7C7F2284231870623C5605 /* TokenScriptCard.swift in Sources */, diff --git a/AlphaWallet/Assets.xcassets/dove.colorset/Contents.json b/AlphaWallet/Assets.xcassets/dove.colorset/Contents.json deleted file mode 100644 index ff5bf4a9e1..0000000000 --- a/AlphaWallet/Assets.xcassets/dove.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - }, - "colors" : [ - { - "idiom" : "universal", - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "0x72", - "alpha" : "1.000", - "blue" : "0x72", - "green" : "0x72" - } - } - } - ] -} \ No newline at end of file diff --git a/AlphaWallet/Assets.xcassets/dusty.colorset/Contents.json b/AlphaWallet/Assets.xcassets/dusty.colorset/Contents.json deleted file mode 100644 index 9de76c226a..0000000000 --- a/AlphaWallet/Assets.xcassets/dusty.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - }, - "colors" : [ - { - "idiom" : "universal", - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "0x99", - "alpha" : "1.000", - "blue" : "0x99", - "green" : "0x99" - } - } - } - ] -} \ No newline at end of file diff --git a/AlphaWallet/Assets.xcassets/iconsSettingsJson.imageset/Contents.json b/AlphaWallet/Assets.xcassets/iconsSettingsJson.imageset/Contents.json new file mode 100644 index 0000000000..d277243fb9 --- /dev/null +++ b/AlphaWallet/Assets.xcassets/iconsSettingsJson.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "iconsSettingsJson.pdf", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/AlphaWallet/Assets.xcassets/iconsSettingsJson.imageset/iconsSettingsJson.pdf b/AlphaWallet/Assets.xcassets/iconsSettingsJson.imageset/iconsSettingsJson.pdf new file mode 100644 index 0000000000..e06aa08210 Binary files /dev/null and b/AlphaWallet/Assets.xcassets/iconsSettingsJson.imageset/iconsSettingsJson.pdf differ diff --git a/AlphaWallet/Assets.xcassets/mine.colorset/Contents.json b/AlphaWallet/Assets.xcassets/mine.colorset/Contents.json deleted file mode 100644 index 930e3e0b99..0000000000 --- a/AlphaWallet/Assets.xcassets/mine.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "info" : { - "version" : 1, - "author" : "xcode" - }, - "colors" : [ - { - "idiom" : "universal", - "color" : { - "color-space" : "srgb", - "components" : { - "red" : "47", - "alpha" : "1.000", - "blue" : "47", - "green" : "47" - } - } - } - ] -} \ No newline at end of file diff --git a/AlphaWallet/Core/Helpers/StringValidator.swift b/AlphaWallet/Core/Helpers/StringValidator.swift new file mode 100644 index 0000000000..8366fa267e --- /dev/null +++ b/AlphaWallet/Core/Helpers/StringValidator.swift @@ -0,0 +1,76 @@ +// +// StringValidator.swift +// AlphaWallet +// +// Created by Jerome Chan on 2/12/21. +// + +import Foundation + +typealias StringValidatorResult = Result + +class StringValidator { + enum Errors: Error { + case list([StringValidator.Rule]) + } + + enum Rule { + case lengthLessThan(Int) + case lengthLessThanOrEqualTo(Int) + case lengthMoreThan(Int) + case lengthMoreThanOrEqualTo(Int) + case doesNotContain(CharacterSet) + case canOnlyContain(CharacterSet) + + func validate(_ inputString: String) -> Bool { + switch self { + case .lengthLessThan(let length): + return inputString.count < length + case .lengthLessThanOrEqualTo(let length): + return inputString.count <= length + case .lengthMoreThan(let length): + return inputString.count > length + case .lengthMoreThanOrEqualTo(let length): + return inputString.count >= length + case .doesNotContain(let set): + return inputString.rangeOfCharacter(from: set) == nil + case .canOnlyContain(let set): + return inputString.rangeOfCharacter(from: set.inverted) == nil + } + } + } + + private var rules: [StringValidator.Rule] + + init() { + self.rules = [] + } + + init(rules: [StringValidator.Rule]) { + self.rules = rules + } + + func validate(string inputString: String) -> StringValidatorResult { + let errors = rules.compactMap { + $0.validate(inputString) ? nil : $0 + } + if errors.isEmpty { + return .success(()) + } + return .failure(.list(errors)) + } + + func containsIllegalCharacters(string inputString: String) -> Bool { + guard case let Result.failure(StringValidator.Errors.list(errors)) = validate(string: inputString) else { return false } + let filteredErrors: [StringValidator.Rule] = errors.compactMap { error in + switch error { + case StringValidator.Rule.lengthLessThan, StringValidator.Rule.lengthLessThanOrEqualTo, StringValidator.Rule.lengthMoreThan, StringValidator.Rule.lengthMoreThanOrEqualTo: + return nil + default: + return error + } + } + return !filteredErrors.isEmpty + } +} + diff --git a/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/Contents.json b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/cod.colorset/Contents.json b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/cod.colorset/Contents.json new file mode 100644 index 0000000000..bfb5070fe7 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/cod.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x14", + "green" : "0x14", + "red" : "0x14" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/dove.colorset/Contents.json b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/dove.colorset/Contents.json new file mode 100644 index 0000000000..c7d856b670 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/dove.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.447", + "green" : "0.447", + "red" : "0.447" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/dusty.colorset/Contents.json b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/dusty.colorset/Contents.json new file mode 100644 index 0000000000..0b7d1dcdf0 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/dusty.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.600", + "green" : "0.600", + "red" : "0.600" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/mine.colorset/Contents.json b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/mine.colorset/Contents.json new file mode 100644 index 0000000000..51e9a219a1 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/mine.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.184", + "green" : "0.184", + "red" : "0.184" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/venus.colorset/Contents.json b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/venus.colorset/Contents.json new file mode 100644 index 0000000000..3a80ff2c92 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/venus.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x23", + "green" : "0x23", + "red" : "0x23" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Assets.xcassets/white.colorset/Contents.json b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/white.colorset/Contents.json similarity index 56% rename from AlphaWallet/Assets.xcassets/white.colorset/Contents.json rename to AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/white.colorset/Contents.json index c2a1cc28ca..fafa476721 100644 --- a/AlphaWallet/Assets.xcassets/white.colorset/Contents.json +++ b/AlphaWallet/Core/Helpers/UIFactory/UIFactoryColors.xcassets/white.colorset/Contents.json @@ -1,20 +1,20 @@ { - "info" : { - "version" : 1, - "author" : "xcode" - }, "colors" : [ { - "idiom" : "universal", "color" : { "color-space" : "srgb", "components" : { - "red" : "0xFF", "alpha" : "1.000", "blue" : "0xFF", - "green" : "0xFF" + "green" : "0xFF", + "red" : "0xFF" } - } + }, + "idiom" : "universal" } - ] -} \ No newline at end of file + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/Contents.json b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/cod.colorset/Contents.json b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/cod.colorset/Contents.json new file mode 100644 index 0000000000..bfb5070fe7 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/cod.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x14", + "green" : "0x14", + "red" : "0x14" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/dove.colorset/Contents.json b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/dove.colorset/Contents.json new file mode 100644 index 0000000000..c7d856b670 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/dove.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.447", + "green" : "0.447", + "red" : "0.447" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/dusty.colorset/Contents.json b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/dusty.colorset/Contents.json new file mode 100644 index 0000000000..0b7d1dcdf0 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/dusty.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.600", + "green" : "0.600", + "red" : "0.600" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/mine.colorset/Contents.json b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/mine.colorset/Contents.json new file mode 100644 index 0000000000..51e9a219a1 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/mine.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.184", + "green" : "0.184", + "red" : "0.184" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/venus.colorset/Contents.json b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/venus.colorset/Contents.json new file mode 100644 index 0000000000..3a80ff2c92 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/venus.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x23", + "green" : "0x23", + "red" : "0x23" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/white.colorset/Contents.json b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/white.colorset/Contents.json new file mode 100644 index 0000000000..fafa476721 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIKitFactory/UIFactoryColors.xcassets/white.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactory.swift b/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactory.swift new file mode 100644 index 0000000000..0e43d1f347 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactory.swift @@ -0,0 +1,77 @@ +// +// UIKitFactory.swift +// AlphaWallet +// +// Created by Jerome Chan on 3/12/21. +// + +import UIKit + +// This protocol allows an object to create commonly used user interface elements. +protocol UIKitFactoryProtocol { + func textFieldHeaderLabel() -> UILabel + func textFieldHeaderLabel(overrides: [UIKitProperty]) -> UILabel + func textField() -> UITextField + func textField(overrides: [UIKitProperty]) -> UITextField + func textView() -> UITextView + func textView(overrides: [UIKitProperty]) -> UITextView +} + +// Default implementations +extension UIKitFactoryProtocol { + func textFieldHeaderLabel() -> UILabel { + return textFieldHeaderLabel(overrides: []) + } + + func textField() -> UITextField { + return textField(overrides: []) + } + + func textView() -> UITextView { + return textView(overrides: []) + } +} + +class UIKitFactory: UIKitFactoryProtocol { + func textFieldHeaderLabel(overrides: [UIKitProperty]) -> UILabel { + let defaults: [UIKitProperty] = [ + .backgroundColor(App.TextFieldHeaderLabel.Color.background), + .font(App.TextFieldHeaderLabel.font), + .textColor(App.TextFieldHeaderLabel.Color.text), + ] + let properties = defaults + overrides + let label = UILabel() + properties.forEach { $0.apply(label) } + label.adjustsFontForContentSizeCategory = true + label.translatesAutoresizingMaskIntoConstraints = false + return label + } + + func textField(overrides: [UIKitProperty]) -> UITextField { + let defaults: [UIKitProperty] = [ + .backgroundColor(App.TextField.Color.background), + .font(App.TextField.font), + .textColor(App.TextField.Color.text), + ] + let properties = defaults + overrides + let textField = UITextField() + properties.forEach { $0.apply(textField) } + textField.adjustsFontForContentSizeCategory = true + textField.translatesAutoresizingMaskIntoConstraints = false + return textField + } + + func textView(overrides: [UIKitProperty]) -> UITextView { + let defaults: [UIKitProperty] = [ + .backgroundColor(App.TextView.Color.background), + .font(App.TextView.font), + .textColor(App.TextView.Color.text), + ] + let properties = defaults + overrides + let textView = UITextView() + properties.forEach { $0.apply(textView) } + textView.adjustsFontForContentSizeCategory = true + textView.translatesAutoresizingMaskIntoConstraints = false + return textView + } +} diff --git a/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactoryColor.swift b/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactoryColor.swift new file mode 100644 index 0000000000..0eb6b53bfb --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactoryColor.swift @@ -0,0 +1,152 @@ +// +// UIKitFactoryColor.swift +// AlphaWallet +// +// Created by Jerome Chan on 7/12/21. +// + +import Foundation +import UIKit + +// TODO: Migrate this to AppStyle + +extension App.Color { + static let background: UIColor = { + if #available(iOS 13, *) { + return UIColor { traitCollection in + switch traitCollection.userInterfaceStyle { + case .unspecified, .light: + return R.color.white()! + case .dark: + return R.color.cod()! + } + } + } else { + return R.color.white()! + } + }() +} + +extension App.NavigationBar.Color { + static let text: UIColor = { + if #available(iOS 13, *) { + return UIColor { traitCollection in + switch traitCollection.userInterfaceStyle { + case .unspecified, .light: + return R.color.black()! + case .dark: + return R.color.white()! + } + } + } else { + return R.color.black()! + } + }() + static let background: UIColor = { + if #available(iOS 13, *) { + return UIColor { traitCollection in + switch traitCollection.userInterfaceStyle { + case .unspecified, .light: + return R.color.white()! + case .dark: + return R.color.cod()! + } + } + } else { + return R.color.white()! + } + }() +} + +extension App.TextFieldHeaderLabel.Color { + static let text: UIColor = { + if #available(iOS 13, *) { + return UIColor { traitCollection in + switch traitCollection.userInterfaceStyle { + case .unspecified, .light: + return R.color.dove()! + case .dark: + return R.color.dusty()! + } + } + } else { + return R.color.dove()! + } + }() + static let background: UIColor = { + if #available(iOS 13, *) { + return UIColor { traitCollection in + switch traitCollection.userInterfaceStyle { + case .unspecified, .light: + return R.color.white()! + case .dark: + return R.color.cod()! + } + } + } else { + return R.color.white()! + } + }() +} + +extension App.TextField.Color { + static let text: UIColor = { + if #available(iOS 13, *) { + return UIColor { traitCollection in + switch traitCollection.userInterfaceStyle { + case .unspecified, .light: + return R.color.mine()! + case .dark: + return R.color.white()! + } + } + } else { + return R.color.mine()! + } + }() + static let background: UIColor = { + if #available(iOS 13, *) { + return UIColor { traitCollection in + switch traitCollection.userInterfaceStyle { + case .unspecified, .light: + return R.color.white()! + case .dark: + return R.color.cod()! + } + } + } else { + return R.color.white()! + } + }() +} + +extension App.TextView.Color { + static let text: UIColor = { + if #available(iOS 13, *) { + return UIColor { traitCollection in + switch traitCollection.userInterfaceStyle { + case .unspecified, .light: + return R.color.mine()! + case .dark: + return R.color.white()! + } + } + } else { + return R.color.mine()! + } + }() + static let background: UIColor = { + if #available(iOS 13, *) { + return UIColor { traitCollection in + switch traitCollection.userInterfaceStyle { + case .unspecified, .light: + return R.color.alabaster()! + case .dark: + return R.color.venus()! + } + } + } else { + return R.color.alabaster()! + } + }() +} diff --git a/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactoryEnums.swift b/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactoryEnums.swift new file mode 100644 index 0000000000..e19fe2c128 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactoryEnums.swift @@ -0,0 +1,65 @@ +// +// UIKitFactoryEnums.swift +// AlphaWallet +// +// Created by Jerome Chan on 6/12/21. +// + +import UIKit + +enum UIKitProperty { + // UIView + case backgroundColor(UIColor) + // UILabel / UITextField / UITextView + case font(UIFont) + case textColor(UIColor) + case textAlignment(NSTextAlignment) + + func apply(_ view: UIView) { + switch self { + case .backgroundColor(let color): + view.backgroundColor = color + default: + break + } + } + + func apply(_ label: UILabel) { + switch self { + case .font(let font): + label.font = font + case .textColor(let color): + label.textColor = color + case .textAlignment(let alignment): + label.textAlignment = alignment + default: + apply(label as UIView) + } + } + + func apply(_ textField: UITextField) { + switch self { + case .font(let font): + textField.font = font + case .textColor(let color): + textField.textColor = color + case .textAlignment(let alignment): + textField.textAlignment = alignment + default: + apply(textField as UIView) + } + } + + func apply(_ textView: UITextView) { + switch self { + case .font(let font): + textView.font = font + case .textColor(let color): + textView.textColor = color + case .textAlignment(let alignment): + textView.textAlignment = alignment + default: + apply(textView as UIView) + } + } +} diff --git a/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactoryFont.swift b/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactoryFont.swift new file mode 100644 index 0000000000..8cd3f6c382 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactoryFont.swift @@ -0,0 +1,26 @@ +// +// UIKitFactoryFont.swift +// AlphaWallet +// +// Created by Jerome Chan on 7/12/21. +// + +import UIKit + +extension App.TextFieldHeaderLabel { + static let font = { + return UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: Fonts.regular(size: 13.0)) + }() +} + +extension App.TextField { + static let font = { + return UIFontMetrics(forTextStyle: .body).scaledFont(for: Fonts.regular(size: 17.0)) + }() +} + +extension App.TextView { + static let font = { + return UIFontMetrics(forTextStyle: .body).scaledFont(for: Fonts.regular(size: 17.0)) + }() +} diff --git a/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactoryStruct.swift b/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactoryStruct.swift new file mode 100644 index 0000000000..3e5bb29999 --- /dev/null +++ b/AlphaWallet/Core/Helpers/UIKitFactory/UIKitFactoryStruct.swift @@ -0,0 +1,24 @@ +// +// UIKitFactoryStruct.swift +// AlphaWallet +// +// Created by Jerome Chan on 7/12/21. +// + +import UIKit + +struct App { + struct Color {} + struct NavigationBar { + struct Color { } + } + struct TextFieldHeaderLabel { + struct Color { } + } + struct TextField { + struct Color { } + } + struct TextView { + struct Color { } + } +} diff --git a/AlphaWallet/Localization/en.lproj/Localizable.strings b/AlphaWallet/Localization/en.lproj/Localizable.strings index 0074fc4e8c..ee7eb4c732 100644 --- a/AlphaWallet/Localization/en.lproj/Localizable.strings +++ b/AlphaWallet/Localization/en.lproj/Localizable.strings @@ -97,6 +97,15 @@ "settings.language.useSystem.title" = "Use System Setting"; "settings.version.label.title" = "Version"; "settings.tokenScriptStandard.title" = "TokenScript Compatibility"; +"settings.advanced.exportJSONKeystore.title" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.title" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.label" = "Your Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.passwordButton.info" = "Set Password for Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.passwordButton.password" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.title" = "Set Password for Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.label" = "Password"; +"settings.advanced.exportJSONKeystore.password.passwordButton.info" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.passwordButton.password" = "Your Keystore JSON"; "settings.general.title" = "General"; "settings.advanced.title" = "Advanced"; "settings.contactUs.title" = "Contact Us"; diff --git a/AlphaWallet/Localization/es.lproj/Localizable.strings b/AlphaWallet/Localization/es.lproj/Localizable.strings index f92cfa9527..ab1c283b1e 100644 --- a/AlphaWallet/Localization/es.lproj/Localizable.strings +++ b/AlphaWallet/Localization/es.lproj/Localizable.strings @@ -97,6 +97,15 @@ "settings.language.useSystem.title" = "Usar la configuración del sistema"; "settings.version.label.title" = "Versión"; "settings.tokenScriptStandard.title" = "Compatibilidad de TokenScript"; +"settings.advanced.exportJSONKeystore.title" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.title" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.label" = "Your Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.passwordButton.info" = "Set Password for Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.passwordButton.password" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.title" = "Set Password for Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.label" = "Password"; +"settings.advanced.exportJSONKeystore.password.passwordButton.info" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.passwordButton.password" = "Your Keystore JSON"; "settings.general.title" = "Generales"; "settings.advanced.title" = "Avanzado"; "settings.contactUs.title" = "Contacta con nosotros"; diff --git a/AlphaWallet/Localization/ja.lproj/Localizable.strings b/AlphaWallet/Localization/ja.lproj/Localizable.strings index f997e2e4b9..62d5281a01 100644 --- a/AlphaWallet/Localization/ja.lproj/Localizable.strings +++ b/AlphaWallet/Localization/ja.lproj/Localizable.strings @@ -97,6 +97,15 @@ "settings.language.useSystem.title" = "システム設定を使用"; "settings.version.label.title" = "バージョン"; "settings.tokenScriptStandard.title" = "TokenScript Compatibility"; +"settings.advanced.exportJSONKeystore.title" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.title" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.label" = "Your Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.passwordButton.info" = "Set Password for Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.passwordButton.password" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.title" = "Set Password for Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.label" = "Password"; +"settings.advanced.exportJSONKeystore.password.passwordButton.info" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.passwordButton.password" = "Your Keystore JSON"; "settings.advanced.title" = "Advanced"; "settings.contactUs.title" = "Contact Us"; "settings.changeCurrency.title" = "Change Currency"; diff --git a/AlphaWallet/Localization/ko.lproj/Localizable.strings b/AlphaWallet/Localization/ko.lproj/Localizable.strings index fe70e56fc0..608f8de1d5 100644 --- a/AlphaWallet/Localization/ko.lproj/Localizable.strings +++ b/AlphaWallet/Localization/ko.lproj/Localizable.strings @@ -97,6 +97,15 @@ "settings.language.useSystem.title" = "시스템 설정 사용"; "settings.version.label.title" = "버전"; "settings.tokenScriptStandard.title" = "TokenScript Compatibility"; +"settings.advanced.exportJSONKeystore.title" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.title" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.label" = "Your Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.passwordButton.info" = "Set Password for Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.passwordButton.password" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.title" = "Set Password for Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.label" = "Password"; +"settings.advanced.exportJSONKeystore.password.passwordButton.info" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.passwordButton.password" = "Your Keystore JSON"; "settings.advanced.title" = "Advanced"; "settings.contactUs.title" = "Contact Us"; "settings.changeCurrency.title" = "Change Currency"; diff --git a/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings b/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings index 797677b843..6803dce14f 100644 --- a/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings +++ b/AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings @@ -97,6 +97,15 @@ "settings.language.useSystem.title" = "使用系统设置"; "settings.version.label.title" = "版本"; "settings.tokenScriptStandard.title" = "TokenScript 兼容性"; +"settings.advanced.exportJSONKeystore.title" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.title" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.label" = "Your Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.passwordButton.info" = "Set Password for Keystore JSON"; +"settings.advanced.exportJSONKeystore.file.passwordButton.password" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.title" = "Set Password for Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.label" = "Password"; +"settings.advanced.exportJSONKeystore.password.passwordButton.info" = "Export Keystore JSON"; +"settings.advanced.exportJSONKeystore.password.passwordButton.password" = "Your Keystore JSON"; "settings.general.title" = "通用"; "settings.advanced.title" = "高级"; "settings.contactUs.title" = "联系我们"; diff --git a/AlphaWallet/Settings/Coordinators/ExportJsonKeystoreCoordinator.swift b/AlphaWallet/Settings/Coordinators/ExportJsonKeystoreCoordinator.swift new file mode 100644 index 0000000000..4c6086072d --- /dev/null +++ b/AlphaWallet/Settings/Coordinators/ExportJsonKeystoreCoordinator.swift @@ -0,0 +1,99 @@ +// +// ExportJsonKeystoreCoordinator.swift +// AlphaWallet +// +// Created by Jerome Chan on 1/12/21. +// + +import Foundation +import UIKit + +@objc protocol ExportJsonKeystoreCoordinatorDelegate { + func didComplete(coordinator: ExportJsonKeystoreCoordinator) +} + +class ExportJsonKeystoreCoordinator: NSObject, Coordinator { + + var coordinators: [Coordinator] = [] + weak var delegate: ExportJsonKeystoreCoordinatorDelegate? + private var keystore: Keystore + private var navigationController: UINavigationController + private weak var initialViewController: UIViewController? + + init(keystore: Keystore, navigationController: UINavigationController) { + self.keystore = keystore + self.navigationController = navigationController + initialViewController = navigationController.topViewController + } + + func start() { + startPasswordViewController(buttonTitle: R.string.localizable.settingsAdvancedExportJSONKeystorePasswordPasswordButtonPassword()) + } + + private func startFileViewController(buttonTitle: String, jsonData: String) { + let controller = ExportJsonKeystoreFileViewController(buttonTitle: buttonTitle, jsonData: jsonData) + controller.fileDelegate = self + navigationController.pushViewController(controller, animated: true) + } + + private func startPasswordViewController(buttonTitle: String) { + let controller = ExportJsonKeystorePasswordViewController(viewModel: ExportJsonKeystorePasswordViewModel(keystore: keystore), buttonTitle: buttonTitle) + controller.passwordDelegate = self + navigationController.pushViewController(controller, animated: true) + } + + private func popViewControllers() { + if let controller = initialViewController { + navigationController.popToViewController(controller, animated: true) + } + } +} + +extension ExportJsonKeystoreCoordinator: ExportJsonKeystoreFileDelegate { + func didExport(jsonData: String, in viewController: UIViewController) { + exportJsonKeystore(jsonData: jsonData, in: viewController) + } + + func didDismissFileController() { + } + + func didFinish() { + self.popViewControllers() + delegate?.didComplete(coordinator: self) + } + + private func exportJsonKeystore(jsonData: String, in viewController: UIViewController) { + let fileName = "alphawallet_keystore_export_\(UUID().uuidString).json" + let fileUrl = FileManager.default.temporaryDirectory.appendingPathComponent(fileName) + do { + try jsonData.data(using: .utf8)!.write(to: fileUrl) + } catch { + navigationController.displayError(error: error) + return + } + let activityViewController = UIActivityViewController(activityItems: [fileUrl], applicationActivities: nil) + activityViewController.completionWithItemsHandler = {_, _, _, activityError in + if let error = activityError { + self.navigationController.displayError(error: error) + } + } + activityViewController.popoverPresentationController?.sourceView = viewController.view + activityViewController.popoverPresentationController?.sourceRect = navigationController.view.centerRect + navigationController.present(activityViewController, animated: true) + } +} + +extension ExportJsonKeystoreCoordinator: ExportJsonKeystorePasswordDelegate { + func didRequestExportKeystore(with jsonData: String) { + startFileViewController(buttonTitle: R.string.localizable.settingsAdvancedExportJSONKeystoreFilePasswordButtonPassword(), jsonData: jsonData) + } + + func didDismissPasswordController() { + } +} + +extension UIViewController { + func isStillInNavigationStack() -> Bool { + return navigationController?.viewControllers.contains(self) ?? false + } +} diff --git a/AlphaWallet/Settings/Coordinators/SettingsCoordinator.swift b/AlphaWallet/Settings/Coordinators/SettingsCoordinator.swift index 91261db684..fa2e2dd471 100644 --- a/AlphaWallet/Settings/Coordinators/SettingsCoordinator.swift +++ b/AlphaWallet/Settings/Coordinators/SettingsCoordinator.swift @@ -172,7 +172,7 @@ extension SettingsCoordinator: SettingsViewControllerDelegate { } func settingsViewControllerAdvancedSettingsSelected(in controller: SettingsViewController) { - let controller = AdvancedSettingsViewController(config: config) + let controller = AdvancedSettingsViewController(keystore: keystore, config: config) controller.delegate = self controller.hidesBottomBarWhenPushed = true navigationController.pushViewController(controller, animated: true) @@ -317,6 +317,13 @@ extension SettingsCoordinator: AdvancedSettingsViewControllerDelegate { coordinator.start() addCoordinator(coordinator) } + + func advancedSettingsViewControllerExportJSONKeystoreSelected(in controller: AdvancedSettingsViewController) { + let coordinator = ExportJsonKeystoreCoordinator(keystore: keystore, navigationController: navigationController) + addCoordinator(coordinator) + coordinator.delegate = self + coordinator.start() + } } extension SettingsCoordinator: ChooseSendPrivateTransactionsProviderViewControllerDelegate { @@ -334,3 +341,9 @@ extension SettingsCoordinator: PingInfuraCoordinatorDelegate { removeCoordinator(self) } } + +extension SettingsCoordinator: ExportJsonKeystoreCoordinatorDelegate { + func didComplete(coordinator: ExportJsonKeystoreCoordinator) { + removeCoordinator(coordinator) + } +} diff --git a/AlphaWallet/Settings/ViewControllers/AdvancedSettingsViewController.swift b/AlphaWallet/Settings/ViewControllers/AdvancedSettingsViewController.swift index 249a17f2a3..e76acdc20a 100644 --- a/AlphaWallet/Settings/ViewControllers/AdvancedSettingsViewController.swift +++ b/AlphaWallet/Settings/ViewControllers/AdvancedSettingsViewController.swift @@ -16,6 +16,7 @@ protocol AdvancedSettingsViewControllerDelegate: AnyObject { func advancedSettingsViewControllerAnalyticsSelected(in controller: AdvancedSettingsViewController) func advancedSettingsViewControllerUsePrivateNetworkSelected(in controller: AdvancedSettingsViewController) func advancedSettingsViewControllerPingInfuraSelected(in controller: AdvancedSettingsViewController) + func advancedSettingsViewControllerExportJSONKeystoreSelected(in controller: AdvancedSettingsViewController) } class AdvancedSettingsViewController: UIViewController { @@ -35,12 +36,18 @@ class AdvancedSettingsViewController: UIViewController { }() private let roundedBackground = RoundedBackground() private var config: Config + private let keystore: Keystore weak var delegate: AdvancedSettingsViewControllerDelegate? - init(config: Config) { + init(keystore: Keystore, config: Config) { self.config = config + self.keystore = keystore super.init(nibName: nil, bundle: nil) + if keystore.currentWallet.isReal() { + viewModel.rows.append(.exportJSONKeystore) + } + roundedBackground.backgroundColor = GroupedTable.Color.background view.addSubview(roundedBackground) @@ -79,7 +86,7 @@ extension AdvancedSettingsViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let row = viewModel.rows[indexPath.row] switch row { - case .analytics, .changeCurrency, .changeLanguage, .clearBrowserCache, .console, .tokenScript, .pingInfura: + case .analytics, .changeCurrency, .changeLanguage, .clearBrowserCache, .console, .tokenScript, .exportJSONKeystore, .pingInfura: let cell: SettingTableViewCell = tableView.dequeueReusableCell(for: indexPath) cell.configure(viewModel: .init(titleText: row.title, subTitleText: nil, icon: row.icon)) return cell @@ -134,6 +141,14 @@ extension AdvancedSettingsViewController: UITableViewDelegate { delegate?.advancedSettingsViewControllerUsePrivateNetworkSelected(in: self) case .pingInfura: delegate?.advancedSettingsViewControllerPingInfuraSelected(in: self) + case .exportJSONKeystore: + delegate?.advancedSettingsViewControllerExportJSONKeystoreSelected(in: self) } } -} \ No newline at end of file +} + +fileprivate extension Wallet { + func isReal() -> Bool { + return type == .real(address) + } +} diff --git a/AlphaWallet/Settings/ViewControllers/ExportJsonKeystoreFileViewController.swift b/AlphaWallet/Settings/ViewControllers/ExportJsonKeystoreFileViewController.swift new file mode 100644 index 0000000000..3e09808dd0 --- /dev/null +++ b/AlphaWallet/Settings/ViewControllers/ExportJsonKeystoreFileViewController.swift @@ -0,0 +1,71 @@ +// +// ExportJsonKeystoreFileViewController.swift +// AlphaWallet +// +// Created by Jerome Chan on 1/12/21. +// + +import UIKit + +@objc protocol ExportJsonKeystoreFileDelegate { + func didExport(jsonData: String, in viewController: UIViewController) + func didFinish() + func didDismissFileController() +} + +class ExportJsonKeystoreFileViewController: UIViewController { + private let buttonTitle: String + private let jsonData: String + private var infoView: ExportJsonKeystoreFileView { + return view as! ExportJsonKeystoreFileView + } + private var exportedData: String? + weak var fileDelegate: ExportJsonKeystoreFileDelegate? + + init(buttonTitle: String, jsonData: String) { + self.buttonTitle = buttonTitle + self.jsonData = jsonData + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + return nil + } + + override func viewDidLoad() { + super.viewDidLoad() + configureController() + configureContent() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + if !isStillInNavigationStack() { + fileDelegate?.didDismissFileController() + } + } + + override func loadView() { + view = ExportJsonKeystoreFileView() + } + + private func configureController() { + navigationItem.title = R.string.localizable.settingsAdvancedExportJSONKeystoreFileTitle() + let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(requestDoneAction(_:))) + navigationItem.rightBarButtonItems = [doneButton] + infoView.setButton(title: buttonTitle) + infoView.addPasswordButtonTarget(self, action: #selector(requestExportAction(_:))) + } + + private func configureContent() { + infoView.set(content: jsonData) + } + + @objc func requestExportAction(_ sender: UIButton?) { + fileDelegate?.didExport(jsonData: jsonData, in: self) + } + + @objc func requestDoneAction(_ sender: UIBarButtonItem) { + fileDelegate?.didFinish() + } +} diff --git a/AlphaWallet/Settings/ViewControllers/ExportJsonKeystorePasswordViewController.swift b/AlphaWallet/Settings/ViewControllers/ExportJsonKeystorePasswordViewController.swift new file mode 100644 index 0000000000..2d0d6b969f --- /dev/null +++ b/AlphaWallet/Settings/ViewControllers/ExportJsonKeystorePasswordViewController.swift @@ -0,0 +1,131 @@ +// +// ExportJsonKeystorePasswordViewController.swift +// AlphaWallet +// +// Created by Jerome Chan on 1/12/21. +// + +import UIKit + +@objc protocol ExportJsonKeystorePasswordDelegate { + func didRequestExportKeystore(with password: String) + func didDismissPasswordController() +} + +class ExportJsonKeystorePasswordViewController: UIViewController { + private let buttonTitle: String + private var viewModel: ExportJsonKeystorePasswordViewModel + private lazy var keyboardChecker = KeyboardChecker(self, resetHeightDefaultValue: 0, ignoreBottomSafeArea: true) + private lazy var queue: DispatchQueue = DispatchQueue(label: "ExportJsonKeystorePasswordViewController") + private var passwordView: ExportJsonKeystorePasswordView { + return view as! ExportJsonKeystorePasswordView + } + weak var passwordDelegate: ExportJsonKeystorePasswordDelegate? + + init(viewModel: ExportJsonKeystorePasswordViewModel, buttonTitle: String) { + self.viewModel = viewModel + self.buttonTitle = buttonTitle + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + return nil + } + + override func viewDidLoad() { + super.viewDidLoad() + configureController() + passwordView.passwordTextField.becomeFirstResponder() + passwordView.disableButton() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + keyboardChecker.viewWillAppear() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + keyboardChecker.viewWillDisappear() + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + if !isStillInNavigationStack() { + passwordDelegate?.didDismissPasswordController() + } + } + + override func loadView() { + view = ExportJsonKeystorePasswordView() + } + + private func configureController() { + navigationItem.title = R.string.localizable.settingsAdvancedExportJSONKeystorePasswordTitle() + passwordView.setButton(title: buttonTitle) + passwordView.addExportButtonTarget(self, action: #selector(requestExportAction(_:))) + passwordView.passwordTextField.delegate = self + keyboardChecker.constraint = passwordView.bottomConstraint + } + + @objc func requestExportAction(_ sender: UIButton?) { + guard let password = passwordView.passwordTextField.text else { return } + switch viewModel.validate(password: password) { + case .success: + computeExportJsonData(password: password) + case .failure: + break + } + } + + private func computeExportJsonData(password: String) { + view.endEditing(true) + navigationController?.displayLoading(text: "Exporting", animated: true) + DispatchQueue.main.async { + self.queue.async { + self.viewModel.computeHash(password: password) { result in + DispatchQueue.main.async { + self.navigationController?.hideLoading() + switch result { + case .success(let jsonData): + self.passwordDelegate?.didRequestExportKeystore(with: jsonData) + case .failure(let error): + self.navigationController?.displayError(error: error) + } + } + } + } + } + } +} + +extension ExportJsonKeystorePasswordViewController: UITextFieldDelegate { + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + guard var currentPasswordString = textField.text, let stringRange = Range(range, in: currentPasswordString) else { return true } + let originalPasswordString = currentPasswordString + currentPasswordString.replaceSubrange(stringRange, with: string) + let validPassword = !viewModel.containsIllegalCharacters(password: currentPasswordString) + if validPassword { + setButtonState(for: currentPasswordString) + return true + } else { + setButtonState(for: originalPasswordString) + return false + } + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return false + } + + func setButtonState(for passwordString: String) { + let state = viewModel.validate(password: passwordString) + switch state { + case .success: + passwordView.enableButton() + case .failure: + passwordView.disableButton() + } + } +} diff --git a/AlphaWallet/Settings/ViewModels/AdvancedSettingsViewModel.swift b/AlphaWallet/Settings/ViewModels/AdvancedSettingsViewModel.swift index f715f7d6b6..74223b07bf 100644 --- a/AlphaWallet/Settings/ViewModels/AdvancedSettingsViewModel.swift +++ b/AlphaWallet/Settings/ViewModels/AdvancedSettingsViewModel.swift @@ -32,6 +32,7 @@ enum AdvancedSettingsRow: CaseIterable { case analytics case usePrivateNetwork case pingInfura + case exportJSONKeystore var title: String { switch self { @@ -51,6 +52,8 @@ enum AdvancedSettingsRow: CaseIterable { return R.string.localizable.settingsChooseSendPrivateTransactionsProviderButtonTitle() case .pingInfura: return R.string.localizable.settingsPingInfuraTitle() + case .exportJSONKeystore: + return R.string.localizable.settingsAdvancedExportJSONKeystoreTitle() } } @@ -73,6 +76,8 @@ enum AdvancedSettingsRow: CaseIterable { case .pingInfura: //TODO need a more appropriate icon, maybe represent diagnostic or (to a lesser degree Infura) return R.image.settings_analytics()! + case .exportJSONKeystore: + return R.image.iconsSettingsJson()! } } } diff --git a/AlphaWallet/Settings/ViewModels/ExportJsonKeystorePasswordViewModel.swift b/AlphaWallet/Settings/ViewModels/ExportJsonKeystorePasswordViewModel.swift new file mode 100644 index 0000000000..f3c31518f5 --- /dev/null +++ b/AlphaWallet/Settings/ViewModels/ExportJsonKeystorePasswordViewModel.swift @@ -0,0 +1,56 @@ +// +// ExportJsonKeystorePasswordViewModel.swift +// AlphaWallet +// +// Created by Jerome Chan on 2/12/21. +// + +import Foundation + +typealias ExportJsonKeystorePasswordViewModelCompletion = (Result) -> Void + +class ExportJsonKeystorePasswordViewModel { + private let validator: StringValidator + private let keystore: Keystore + + init(keystore: Keystore) { + self.validator = StringValidator(rules: [ + .lengthMoreThanOrEqualTo(6), + .canOnlyContain(CharacterSet.alphanumerics) + ]) + self.keystore = keystore + } + + init(keystore: Keystore, validator: StringValidator) { + self.validator = validator + self.keystore = keystore + } + + func validate(password: String) -> StringValidatorResult { + return validator.validate(string: password) + } + + func containsIllegalCharacters(password: String) -> Bool { + return validator.containsIllegalCharacters(string: password) + } + + func computeHash(password: String, completion: @escaping ExportJsonKeystorePasswordViewModelCompletion) { + if keystore.isHdWallet(wallet: keystore.currentWallet) { + // TODO: Convert from HDWallet into Non-HDWallet then export + completion(.failure(.userCancelled)) + } else { + export(password: password, completion: completion) + } + } + + private func export(password: String, completion: @escaping ExportJsonKeystorePasswordViewModelCompletion) { + keystore.exportRawPrivateKeyForNonHdWalletForBackup(forAccount: keystore.currentWallet.address, newPassword: password) { result in + switch result { + case .success(let rawKey): + completion(.success(rawKey)) + case .failure(let error): + completion(.failure(error)) + } + } + } +} diff --git a/AlphaWallet/Settings/Views/ExportJsonKeystoreFileView.swift b/AlphaWallet/Settings/Views/ExportJsonKeystoreFileView.swift new file mode 100644 index 0000000000..830a229107 --- /dev/null +++ b/AlphaWallet/Settings/Views/ExportJsonKeystoreFileView.swift @@ -0,0 +1,99 @@ +// +// ExportJsonKeystoreFileView.swift +// AlphaWallet +// +// Created by Jerome Chan on 1/12/21. +// + +import UIKit + +class ExportJsonKeystoreFileView: UIView { + private lazy var label: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.adjustsFontForContentSizeCategory = true + label.backgroundColor = R.color.white()! + label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: Fonts.regular(size: 13.0)) + label.textColor = R.color.dove()! + label.text = R.string.localizable.settingsAdvancedExportJSONKeystoreFileLabel() + label.heightAnchor.constraint(equalToConstant: 22.0).isActive = true + return label + }() + private lazy var textView: UITextView = { + let textView = UITextView() + textView.translatesAutoresizingMaskIntoConstraints = false + textView.adjustsFontForContentSizeCategory = true + textView.backgroundColor = R.color.alabaster()! + textView.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: Fonts.regular(size: 17.0)) + textView.textColor = R.color.mine()! + textView.borderColor = R.color.silver() + textView.cornerRadius = 5.0 + textView.borderWidth = 1.0 + textView.isEditable = false + textView.isScrollEnabled = false + textView.isSelectable = true + textView.spellCheckingType = .no + textView.autocorrectionType = .no + textView.contentInset = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 16.0, right: 16.0) + return textView + }() + private let buttonsBar = ButtonsBar(configuration: .green(buttons: 1)) + + convenience init() { + self.init(frame: .zero) + } + + override init(frame: CGRect) { + super.init(frame: frame) + configureView() + } + + required init?(coder: NSCoder) { + return nil + } + + func addPasswordButtonTarget(_ target: Any?, action: Selector) { + let button = buttonsBar.buttons[0] + button.removeTarget(target, action: action, for: .touchUpInside) + button.addTarget(target, action: action, for: .touchUpInside) + } + + func set(content: String) { + textView.text = content + } + + func setButton(title: String) { + buttonsBar.buttons[0].setTitle(title, for: .normal) + } + + private func configureView() { + backgroundColor = R.color.white()! + let footerBar = configureButtonsBar() + footerBar.backgroundColor = R.color.white() + addSubview(label) + addSubview(textView) + addSubview(footerBar) + NSLayoutConstraint.activate([ + label.topAnchor.constraint(equalTo: topAnchor, constant: 34.0), + label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16.0), + label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16.0), + label.bottomAnchor.constraint(equalTo: textView.topAnchor, constant: -4.0), + + textView.leadingAnchor.constraint(equalTo: label.leadingAnchor), + textView.trailingAnchor.constraint(equalTo: label.trailingAnchor), + textView.bottomAnchor.constraint(lessThanOrEqualTo: footerBar.topAnchor, constant: -8.0), + + footerBar.leadingAnchor.constraint(equalTo: leadingAnchor), + footerBar.trailingAnchor.constraint(equalTo: trailingAnchor), + footerBar.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor), + + ]) + } + + private func configureButtonsBar() -> ButtonsBarBackgroundView { + buttonsBar.configure() + let edgeInsets = UIEdgeInsets(top: 16.0, left: 0.0, bottom: 0.0, right: 0.0) + let footerBar = ButtonsBarBackgroundView(buttonsBar: buttonsBar, edgeInsets: edgeInsets, separatorHeight: 1.0) + return footerBar + } +} diff --git a/AlphaWallet/Settings/Views/ExportJsonKeystorePasswordView.swift b/AlphaWallet/Settings/Views/ExportJsonKeystorePasswordView.swift new file mode 100644 index 0000000000..5ed59116c6 --- /dev/null +++ b/AlphaWallet/Settings/Views/ExportJsonKeystorePasswordView.swift @@ -0,0 +1,151 @@ +// +// ExportJsonKeystorePasswordView.swift +// AlphaWallet +// +// Created by Jerome Chan on 1/12/21. +// + +import UIKit + +class ExportJsonKeystorePasswordView: UIView { + private lazy var label: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + label.adjustsFontForContentSizeCategory = true + label.backgroundColor = R.color.white()! + label.font = UIFontMetrics(forTextStyle: .subheadline).scaledFont(for: Fonts.regular(size: 13.0)) + label.textColor = R.color.dove()! + label.text = R.string.localizable.settingsAdvancedExportJSONKeystorePasswordLabel() + label.heightAnchor.constraint(equalToConstant: 22.0).isActive = true + return label + }() + lazy var passwordTextField: UITextField = { + let textField: UITextField = UITextField() + textField.translatesAutoresizingMaskIntoConstraints = false + textField.adjustsFontForContentSizeCategory = true + textField.backgroundColor = R.color.white()! + textField.font = UIFontMetrics(forTextStyle: .body).scaledFont(for: Fonts.regular(size: 17.0)) + textField.textColor = R.color.mine()! + textField.borderStyle = .roundedRect + textField.isSecureTextEntry = true + textField.placeholder = R.string.localizable.enterPasswordPasswordTextFieldPlaceholder() + textField.autocorrectionType = .no + textField.spellCheckingType = .no + _ = textField.heightAnchor.constraint(equalToConstant: 50.0).isActive = true + textField.borderColor = R.color.azure() + textField.borderWidth = 1.0 + textField.layer.cornerRadius = 5.0 + textField.returnKeyType = .done + textField.spellCheckingType = .no + textField.autocorrectionType = .no + return textField + }() + private let buttonsBar = ButtonsBar(configuration: .green(buttons: 1)) + + var bottomConstraint: NSLayoutConstraint? + + convenience init() { + self.init(frame: .zero) + } + + override init(frame: CGRect) { + super.init(frame: frame) + configureView() + } + + required init?(coder: NSCoder) { + return nil + } + + func addExportButtonTarget(_ target: Any?, action: Selector) { + let button = buttonsBar.buttons[0] + button.removeTarget(target, action: action, for: .touchUpInside) + button.addTarget(target, action: action, for: .touchUpInside) + } + + func setButton(title: String) { + buttonsBar.buttons[0].setTitle(title, for: .normal) + } + + func enableButton() { + buttonsBar.buttons[0].isEnabled = true + } + + func disableButton() { + buttonsBar.buttons[0].isEnabled = false + } + + private func configureView() { + backgroundColor = R.color.white()! + configureTapToDismissKeyboard() + configurePasswordTextField() + let footerBar = configureButtonsBar() + footerBar.backgroundColor = R.color.white() + addSubview(label) + addSubview(passwordTextField) + addSubview(footerBar) + bottomConstraint = footerBar.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: 0) + NSLayoutConstraint.activate([ + label.topAnchor.constraint(equalTo: topAnchor, constant: 34.0), + label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16.0), + label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16.0), + label.bottomAnchor.constraint(equalTo: passwordTextField.topAnchor, constant: -4.0), + + passwordTextField.leadingAnchor.constraint(equalTo: label.leadingAnchor), + passwordTextField.trailingAnchor.constraint(equalTo: label.trailingAnchor), + passwordTextField.bottomAnchor.constraint(lessThanOrEqualTo: footerBar.topAnchor, constant: -4.0), + + footerBar.leadingAnchor.constraint(equalTo: leadingAnchor), + footerBar.trailingAnchor.constraint(equalTo: trailingAnchor), + bottomConstraint! + ]) + } + + private func configurePasswordTextField() { + let buttonFrame = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 30.0, height: 30.0)) + buttonFrame.translatesAutoresizingMaskIntoConstraints = false + let button = UIButton(type: .system) + button.translatesAutoresizingMaskIntoConstraints = false + button.frame = CGRect(x: 0.0, y: 0.0, width: 24.0, height: 24.0) + button.setImage(R.image.togglePassword(), for: .normal) + button.tintColor = .init(red: 111, green: 111, blue: 111) + button.addTarget(self, action: #selector(toggleMaskPassword), for: .touchUpInside) + buttonFrame.addSubview(button) + NSLayoutConstraint.activate([ + buttonFrame.heightAnchor.constraint(equalToConstant: 32.0), + buttonFrame.widthAnchor.constraint(equalToConstant: 32.0), + buttonFrame.centerYAnchor.constraint(equalTo: button.centerYAnchor), + button.widthAnchor.constraint(equalToConstant: 24.0), + button.heightAnchor.constraint(equalToConstant: 24.0), + buttonFrame.leadingAnchor.constraint(equalTo: button.leadingAnchor), + ]) + passwordTextField.rightViewMode = .always + passwordTextField.rightView = buttonFrame + } + + private func configureButtonsBar() -> ButtonsBarBackgroundView { + buttonsBar.configure() + let edgeInsets = UIEdgeInsets(top: 16.0, left: 0.0, bottom: 0.0, right: 0.0) + let footerBar = ButtonsBarBackgroundView(buttonsBar: buttonsBar, edgeInsets: edgeInsets, separatorHeight: 1.0) + return footerBar + } + + @objc private func toggleMaskPassword() { + passwordTextField.isSecureTextEntry = !passwordTextField.isSecureTextEntry + guard let button = passwordTextField.rightView as? UIButton else { return } + if passwordTextField.isSecureTextEntry { + button.tintColor = Colors.navigationTitleColor + } else { + button.tintColor = .init(red: 111, green: 111, blue: 111) + } + } + + private func configureTapToDismissKeyboard() { + let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) + self.addGestureRecognizer(tap) + } + + @objc private func handleTap(_ sender: Any) { + endEditing(true) + } +} diff --git a/AlphaWallet/Style/AppStyle.swift b/AlphaWallet/Style/AppStyle.swift index 93df9c8463..54c851623e 100644 --- a/AlphaWallet/Style/AppStyle.swift +++ b/AlphaWallet/Style/AppStyle.swift @@ -7,7 +7,7 @@ func applyStyle() { UIBarButtonItem.appearance(whenContainedInInstancesOf: [UIDocumentBrowserViewController.self]).tintColor = Colors.navigationButtonTintColor UIWindow.appearance().tintColor = Colors.appTint UITabBar.appearance().tintColor = Colors.appTint - UINavigationBar.appearance().barTintColor = Colors.appBackground + UINavigationBar.appearance().barTintColor = R.color.white()! UINavigationBar.appearance().backIndicatorImage = R.image.backWhite() UINavigationBar.appearance().backIndicatorTransitionMaskImage = R.image.backWhite() UINavigationBar.appearance().titleTextAttributes = [ @@ -23,14 +23,14 @@ func applyStyle() { if #available(iOS 15.0, *) { let appearance = UINavigationBarAppearance() appearance.configureWithOpaqueBackground() - appearance.backgroundColor = Colors.appBackground + appearance.backgroundColor = R.color.white()! appearance.setBackIndicatorImage(R.image.backWhite(), transitionMaskImage: R.image.backWhite()) appearance.titleTextAttributes = [ - .foregroundColor: Colors.navigationTitleColor, + .foregroundColor: R.color.black()!, .font: Fonts.semibold(size: 17) as Any ] appearance.largeTitleTextAttributes = [ - .foregroundColor: Colors.navigationTitleColor, + .foregroundColor: R.color.black()!, .font: Fonts.bold(size: 36) as Any, ] UINavigationBar.appearance().standardAppearance = appearance diff --git a/AlphaWalletTests/Verifications/StringValidatorRuleTestCases.swift b/AlphaWalletTests/Verifications/StringValidatorRuleTestCases.swift new file mode 100644 index 0000000000..e23a0ebf36 --- /dev/null +++ b/AlphaWalletTests/Verifications/StringValidatorRuleTestCases.swift @@ -0,0 +1,61 @@ +// +// StringValidatorRuleTestCases.swift +// AlphaWalletTests +// +// Created by Jerome Chan on 2/12/21. +// + +import XCTest +@testable import AlphaWallet + +class StringValidatorRuleTestCases: XCTestCase { + func testLess() throws { + let less = StringValidator.Rule.lengthLessThan(5) + let lessOrEqual = StringValidator.Rule.lengthLessThanOrEqualTo(5) + var testString = "1234" + XCTAssert(less.validate(testString)) + XCTAssert(lessOrEqual.validate(testString)) + testString = "12345" + XCTAssertFalse(less.validate(testString)) + XCTAssert(lessOrEqual.validate(testString)) + testString = "123456" + XCTAssertFalse(less.validate(testString)) + XCTAssertFalse(lessOrEqual.validate(testString)) + } + + func testMore() throws { + let more = StringValidator.Rule.lengthMoreThan(5) + let moreOrEqual = StringValidator.Rule.lengthMoreThanOrEqualTo(5) + var testString = "1234" + XCTAssertFalse(more.validate(testString)) + XCTAssertFalse(moreOrEqual.validate(testString)) + testString = "12345" + XCTAssertFalse(more.validate(testString)) + XCTAssert(moreOrEqual.validate(testString)) + testString = "123456" + XCTAssert(more.validate(testString)) + XCTAssert(moreOrEqual.validate(testString)) + } + + func testContains() throws { + let cs1 = CharacterSet(charactersIn: "0123456789") + let cs2 = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyz") + let can = StringValidator.Rule.canOnlyContain(cs1) + let cannot = StringValidator.Rule.doesNotContain(cs2) + var testString = " " + XCTAssertFalse(can.validate(testString)) + XCTAssert(cannot.validate(testString)) + testString = "12345" + XCTAssert(can.validate(testString)) + XCTAssert(cannot.validate(testString)) + testString = "abcde" + XCTAssertFalse(can.validate(testString)) + XCTAssertFalse(cannot.validate(testString)) + testString = "12345abcde" + XCTAssertFalse(can.validate(testString)) + XCTAssertFalse(cannot.validate(testString)) + testString = "" + XCTAssert(can.validate(testString)) + XCTAssert(cannot.validate(testString)) + } +} diff --git a/AlphaWalletTests/Verifications/StringValidatorTestCases.swift b/AlphaWalletTests/Verifications/StringValidatorTestCases.swift new file mode 100644 index 0000000000..bf95971073 --- /dev/null +++ b/AlphaWalletTests/Verifications/StringValidatorTestCases.swift @@ -0,0 +1,125 @@ +// +// StringValidatorTestCases.swift +// AlphaWalletTests +// +// Created by Jerome Chan on 2/12/21. +// + +import XCTest +@testable import AlphaWallet + +class StringValidatorTestCases: XCTestCase { + var validator: StringValidator! + override func setUp() { + validator = StringValidator(rules: [ + .lengthLessThanOrEqualTo(10), .lengthMoreThanOrEqualTo(4), .canOnlyContain(.decimalDigits)]) + } + + override func tearDown() { + validator = nil + } + + // All 3 rules pass + func testSuccess() throws { + switch validator.validate(string: "1234567890") { + case .success: + break + case .failure: + XCTFail() + } + } + + // 1 rule failed + func testLessFailure() throws { + switch validator.validate(string: "12345678901") { + case .success: + XCTFail() + case .failure(let StringValidator.Errors.list(failures)): + for failure in failures { + switch failure { + case .lengthLessThanOrEqualTo: + continue + default: + XCTFail("\(failures)") + } + } + } + } + + func testMoreFailure() throws { + switch validator.validate(string: "123") { + case .success: + XCTFail() + case .failure(let StringValidator.Errors.list(failures)): + for failure in failures { + switch failure { + case .lengthMoreThanOrEqualTo: + continue + default: + XCTFail("\(failures)") + } + } + } + } + + func testContainFailure() throws { + switch validator.validate(string: "ABCDEFGHIJ") { + case .success: + XCTFail() + case .failure(let StringValidator.Errors.list(failures)): + for failure in failures { + switch failure { + case .canOnlyContain: + continue + default: + XCTFail("\(failures)") + } + } + } + } + + // 2 rules failed + func testLessContainFailure() throws { + switch validator.validate(string: "ABCDEFGHIJKLMNOP") { + case .success: + XCTFail() + case .failure(let StringValidator.Errors.list(failures)): + for failure in failures { + switch failure { + case .lengthMoreThanOrEqualTo: + XCTFail("\(failures)") + default: + continue + } + } + } + } + + func testMoreContainFailure() throws { + switch validator.validate(string: "ABC") { + case .success: + XCTFail() + case .failure(let StringValidator.Errors.list(failures)): + for failure in failures { + switch failure { + case .lengthLessThanOrEqualTo: + XCTFail("\(failures)") + default: + continue + } + } + } + } + + // Impossible for 3 rules to fail + + func testIllegalCharacters() throws { + let cs1 = CharacterSet(charactersIn: "12345") + validator = StringValidator(rules: [ + .doesNotContain(cs1) + ]) + XCTAssert(validator.containsIllegalCharacters(string: "12345")) + XCTAssertFalse(validator.containsIllegalCharacters(string: "67890")) + XCTAssert(validator.containsIllegalCharacters(string: "1234567890")) + } +}