From 2599093ff7718aea2d4befb3aeed0d60288da45c Mon Sep 17 00:00:00 2001 From: James Chen Date: Sun, 11 Nov 2018 09:32:11 +0900 Subject: [PATCH 001/315] Update AppChainSwift to 0.20.1 --- Neuron.xcodeproj/project.pbxproj | 2 -- Neuron/Sections/Dapp/DAppAction.swift | 10 +++---- .../Services/AppChain/AppChainTxSender.swift | 14 +++++----- .../AppChain/NervosNativeTokenService.swift | 26 +++++++++---------- .../Common/TransactionHistoryService.swift | 7 +++-- .../Services/Common/TransactionService.swift | 6 ++--- Podfile | 2 +- Podfile.lock | 16 +++++------- 8 files changed, 38 insertions(+), 45 deletions(-) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 0d4f9b2a..d4cccda7 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -1186,7 +1186,6 @@ "${BUILT_PRODUCTS_DIR}/RSKPlaceholderTextView/RSKPlaceholderTextView.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", - "${BUILT_PRODUCTS_DIR}/Result/Result.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SensorsAnalyticsSDK/SensorsAnalyticsSDK.framework", "${BUILT_PRODUCTS_DIR}/SipHash/SipHash.framework", @@ -1216,7 +1215,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RSKPlaceholderTextView.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Result.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SensorsAnalyticsSDK.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SipHash.framework", diff --git a/Neuron/Sections/Dapp/DAppAction.swift b/Neuron/Sections/Dapp/DAppAction.swift index 7e61a17f..a7738d92 100644 --- a/Neuron/Sections/Dapp/DAppAction.swift +++ b/Neuron/Sections/Dapp/DAppAction.swift @@ -38,10 +38,9 @@ struct DAppAction { } let appChain = AppChainNetwork.appChain(url: url) DispatchQueue.global().async { - let result = appChain.rpc.getMetaData() - DispatchQueue.main.async { - switch result { - case .success(let metaData): + do { + let metaData = try appChain.rpc.getMetaData() + DispatchQueue.main.async { let tokenModel = TokenModel() tokenModel.address = "" tokenModel.chainId = metaData.chainId.description @@ -54,9 +53,8 @@ struct DAppAction { tokenModel.chainidName = metaData.chainName + metaData.chainId.description tokenModel.chainHosts = chainNode self.saveToken(model: tokenModel) - case .failure: - break } + } catch { } } } diff --git a/Neuron/Services/AppChain/AppChainTxSender.swift b/Neuron/Services/AppChain/AppChainTxSender.swift index 897acc66..66f1ac28 100644 --- a/Neuron/Services/AppChain/AppChainTxSender.swift +++ b/Neuron/Services/AppChain/AppChainTxSender.swift @@ -38,7 +38,7 @@ class AppChainTxSender { let nonce = UUID().uuidString let appChain = AppChainNetwork.appChain() - guard case .success(let blockNumber) = appChain.rpc.blockNumber() else { + guard let blockNumber = try? appChain.rpc.blockNumber() else { throw SendTransactionError.createTransactionIssue } let transaction = Transaction( @@ -48,22 +48,24 @@ class AppChainTxSender { validUntilBlock: blockNumber + UInt64(88), data: data, value: amount, - chainId: UInt32(chainId), + chainId: chainId.description, version: UInt32(0) ) let signed = try sign(transaction: transaction, password: password) - guard case .success(let result) = appChain.rpc.sendRawTransaction(signedTx: signed) else { + do { + return try appChain.rpc.sendRawTransaction(signedTx: signed) + } catch { throw SendTransactionError.signTXFailed } - return result.hash.toHexString() } func sendToken(transaction: Transaction, password: String) throws -> TxHash { let signed = try sign(transaction: transaction, password: password) - guard case .success(let result) = appChain.rpc.sendRawTransaction(signedTx: signed) else { + do { + return try appChain.rpc.sendRawTransaction(signedTx: signed) + } catch { throw SendTransactionError.signTXFailed } - return result.hash.toHexString() } func sign(transaction: Transaction, password: String) throws -> String { diff --git a/Neuron/Services/AppChain/NervosNativeTokenService.swift b/Neuron/Services/AppChain/NervosNativeTokenService.swift index 0788f9d9..5d443698 100644 --- a/Neuron/Services/AppChain/NervosNativeTokenService.swift +++ b/Neuron/Services/AppChain/NervosNativeTokenService.swift @@ -14,10 +14,9 @@ struct NervosNativeTokenService { static func getNervosNativeTokenMsg(blockNumber: String = "latest", completion: @escaping (AppChainServiceResult) -> Void) { let appChain = AppChainNetwork.appChain() DispatchQueue.global().async { - let result = appChain.rpc.getMetaData(blockNumber: blockNumber) - DispatchQueue.main.async { - switch result { - case .success(let metaData): + do { + let metaData = try appChain.rpc.getMetaData(blockNumber: blockNumber) + DispatchQueue.main.async { let tokenModel = TokenModel() tokenModel.address = "" tokenModel.chainId = metaData.chainId.description @@ -29,7 +28,9 @@ struct NervosNativeTokenService { tokenModel.decimals = NativeDecimals.nativeTokenDecimals tokenModel.chainidName = metaData.chainName + metaData.chainId.description completion(AppChainServiceResult.success(tokenModel)) - case .failure(let error): + } + } catch let error { + DispatchQueue.main.async { completion(AppChainServiceResult.error(error)) } } @@ -39,13 +40,13 @@ struct NervosNativeTokenService { static func getNervosNativeTokenBalance(walletAddress: String, completion: @escaping (AppChainServiceResult) -> Void) { let appChain = AppChainNetwork.appChain() DispatchQueue.global().async { - let result = appChain.rpc.getBalance(address: Address(walletAddress)!) - DispatchQueue.main.async { - switch result { - case .success(let balance): - let balanceNumber = self.formatBalanceValue(value: balance) - completion(AppChainServiceResult.success(balanceNumber)) - case .failure(let error): + do { + let balance = try appChain.rpc.getBalance(address: Address(walletAddress)!) + DispatchQueue.main.async { + completion(AppChainServiceResult.success(self.formatBalanceValue(value: balance))) + } + } catch let error { + DispatchQueue.main.async { completion(AppChainServiceResult.error(error)) } } @@ -57,5 +58,4 @@ struct NervosNativeTokenService { let finalValue = Double(format)! return finalValue.clean } - } diff --git a/Neuron/Services/Common/TransactionHistoryService.swift b/Neuron/Services/Common/TransactionHistoryService.swift index 9cd15516..237b03e0 100644 --- a/Neuron/Services/Common/TransactionHistoryService.swift +++ b/Neuron/Services/Common/TransactionHistoryService.swift @@ -49,11 +49,10 @@ class TransactionHistoryService { func getAppChainQuotaPrice() { let appChain = AppChainNetwork.appChain(url: URL(string: token.chainHosts)!) - let result = Utils.getQuotaPrice(appChain: appChain) - switch result { - case .success(let quotaPrice): + do { + let quotaPrice = try Utils.getQuotaPrice(appChain: appChain) self.quotaPrice = Double(quotaPrice) - case .failure: + } catch { self.quotaPrice = pow(10, 9) } } diff --git a/Neuron/Services/Common/TransactionService.swift b/Neuron/Services/Common/TransactionService.swift index d2b8c282..e537d0de 100644 --- a/Neuron/Services/Common/TransactionService.swift +++ b/Neuron/Services/Common/TransactionService.swift @@ -194,8 +194,8 @@ extension TransactionService { class AppChain: TransactionService { override func requestGasCost() { self.gasLimit = 21_000 - let result = try? Utils.getQuotaPrice(appChain: AppChainNetwork.appChain()).dematerialize() - estimatedGasPrice = result?.words.first ?? 1 + let quotaPrice = try? Utils.getQuotaPrice(appChain: AppChainNetwork.appChain()) + estimatedGasPrice = quotaPrice?.words.first ?? 1 changeGasLimitEnable = false changeGasPriceEnable = false } @@ -228,7 +228,7 @@ extension TransactionService { class AppChainERC20: TransactionService { override func requestGasCost() { self.gasLimit = 100_000 - let bigNumber = try? Utils.getQuotaPrice(appChain: AppChainNetwork.appChain()).dematerialize() + let bigNumber = try? Utils.getQuotaPrice(appChain: AppChainNetwork.appChain()) estimatedGasPrice = bigNumber?.words.first ?? 1 changeGasLimitEnable = false changeGasPriceEnable = false diff --git a/Podfile b/Podfile index ca385d09..ee9a0f37 100644 --- a/Podfile +++ b/Podfile @@ -5,7 +5,7 @@ target 'Neuron' do use_frameworks! inhibit_all_warnings! - pod 'AppChainSwift', git: "https://github.com/cryptape/appchain-swift", tag: "v0.19.6" + pod 'AppChainSwift', git: "https://github.com/cryptape/appchain-swift", tag: "v0.20.1" pod 'web3swift', "~> 2.0.1" pod 'RealmSwift' diff --git a/Podfile.lock b/Podfile.lock index fa8fb3b2..d02d2aa8 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,10 +1,9 @@ PODS: - Alamofire (4.7.3) - - AppChainSwift (0.19.6): + - AppChainSwift (0.20.1): - BigInt (~> 3.1) - CryptoSwift (~> 0.13) - PromiseKit (~> 6.5) - - Result (~> 4.0) - secp256k1_swift (~> 1.0.3) - SwiftProtobuf (~> 1.2.0) - BigInt (3.1.0): @@ -37,7 +36,6 @@ PODS: - Realm/Headers (3.11.0) - RealmSwift (3.11.0): - Realm (= 3.11.0) - - Result (4.0.0) - RSKPlaceholderTextView (4.0.0) - scrypt (2.0): - CryptoSwift (~> 0.11) @@ -65,7 +63,7 @@ PODS: DEPENDENCIES: - Alamofire - - AppChainSwift (from `https://github.com/cryptape/appchain-swift`, tag `v0.19.6`) + - AppChainSwift (from `https://github.com/cryptape/appchain-swift`, tag `v0.20.1`) - EFQRCode - IGIdenticon (~> 0.6) - IQKeyboardManagerSwift @@ -97,7 +95,6 @@ SPEC REPOS: - QRCodeReader.swift - Realm - RealmSwift - - Result - RSKPlaceholderTextView - scrypt - SDWebImage @@ -111,7 +108,7 @@ SPEC REPOS: EXTERNAL SOURCES: AppChainSwift: :git: https://github.com/cryptape/appchain-swift - :tag: v0.19.6 + :tag: v0.20.1 SensorsAnalyticsSDK: :branch: v1.10.15 :git: https://github.com/sensorsdata/sa-sdk-ios @@ -119,14 +116,14 @@ EXTERNAL SOURCES: CHECKOUT OPTIONS: AppChainSwift: :git: https://github.com/cryptape/appchain-swift - :tag: v0.19.6 + :tag: v0.20.1 SensorsAnalyticsSDK: :commit: f61088a6cdc661a6e5a3e9f361d624c48b50af01 :git: https://github.com/sensorsdata/sa-sdk-ios SPEC CHECKSUMS: Alamofire: c7287b6e5d7da964a70935e5db17046b7fde6568 - AppChainSwift: dede5206f36322d822e84b697fdb8bdb9d5ba4ea + AppChainSwift: c576e17a9f7433f6c69b626969bd21b6a2db4573 BigInt: 76b5dfdfa3e2e478d4ffdf161aeede5502e2742f CryptoSwift: 16e78bebf567bad1c87b2d58f6547f25b74c31aa EFQRCode: 51b67bd10952da9ef619ae0a396abb746b94263f @@ -141,7 +138,6 @@ SPEC CHECKSUMS: QRCodeReader.swift: 96292a5612fbc2fd9a0b26f93fa5164c8d02f59d Realm: 92f09a102692b96a9a10e9617f214f15c5ab85fc RealmSwift: 5f0481cd658bb751c509314b964a35eaa264d2cf - Result: 7645bb3f50c2ce726dd0ff2fa7b6f42bbe6c3713 RSKPlaceholderTextView: 15f1dc1883e103ea605874b863f19ff6b08496d5 scrypt: 3fe5b1a3b0976f97cd87488673a8f7c65708cc84 SDWebImage: 624d6e296c69b244bcede364c72ae0430ac14681 @@ -153,6 +149,6 @@ SPEC CHECKSUMS: Toast-Swift: 594b5c5e5129f15438e410207e43287f027b3c00 web3swift: 33f30ca0e061e0f89117dfb46f5ee9f626eff6d6 -PODFILE CHECKSUM: 99f6c6788044389220317c30b15309a4efb868ec +PODFILE CHECKSUM: 269ee06fe70f34b43d27938fb94c91addde4b51a COCOAPODS: 1.5.3 From 322337ecca8269fb5bd02feab3dadf5d3e6cead5 Mon Sep 17 00:00:00 2001 From: James Chen Date: Sun, 11 Nov 2018 10:16:52 +0900 Subject: [PATCH 002/315] Add tx amount conversion issue note --- Neuron/Services/Common/TransactionService.swift | 8 ++++---- Neuron/Services/Ethereum/EthereumTxSender.swift | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Neuron/Services/Common/TransactionService.swift b/Neuron/Services/Common/TransactionService.swift index e537d0de..5a6caac6 100644 --- a/Neuron/Services/Common/TransactionService.swift +++ b/Neuron/Services/Common/TransactionService.swift @@ -54,7 +54,7 @@ class TransactionService { var toAddress = "" var amount = 0.0 var extraData = Data() - var password: String = "" // TODO: Inject web3 instance instead of passing password. + var password: String = "" var isUseQRCode = false // TODO: Fix spelling. var estimatedGasPrice: UInt = 1 { didSet { @@ -141,7 +141,7 @@ extension TransactionService { let sender = EthereumTxSender(web3: web3, from: fromAddress) let txhash = try sender.sendETH( to: toAddress, - amount: String(format: "%.18lf", amount), + amount: String(format: "%.18lf", amount), // TODO: Fix this. Use BigUInt!!! gasLimit: gasLimit, gasPrice: BigUInt(gasPrice), data: extraData, @@ -176,7 +176,7 @@ extension TransactionService { // TODO: estimate gas let txhash = try sender.sendToken( to: toAddress, - amount: String(format: "%.18lf", amount), + amount: String(format: "%.18lf", amount), // TODO: Inject web3 instance instead of passing password. gasLimit: gasLimit, gasPrice: BigUInt(gasPrice), contractAddress: token.address, @@ -212,7 +212,7 @@ extension TransactionService { to: toAddress, quota: BigUInt(UInt(gasLimit)), data: extraData, - value: String(format: "%.18lf", amount), + value: String(format: "%.18lf", amount), // TODO: Inject web3 instance instead of passing password. chainId: BigUInt(token.chainId)!, password: password ) diff --git a/Neuron/Services/Ethereum/EthereumTxSender.swift b/Neuron/Services/Ethereum/EthereumTxSender.swift index b0f4f9b9..950a97ce 100644 --- a/Neuron/Services/Ethereum/EthereumTxSender.swift +++ b/Neuron/Services/Ethereum/EthereumTxSender.swift @@ -78,6 +78,7 @@ class EthereumTxSender { options.from = fromAddress do { + // TODO: replace this with `sendERC20tokensWithKnownDecimals`. guard let transaction = try web3.eth.sendERC20tokensWithNaturalUnits( tokenAddress: tokenAddress, from: fromAddress, From 9d6039acdc3112bfa78ad7ce13ebba6db1f4d63d Mon Sep 17 00:00:00 2001 From: James Chen Date: Sun, 11 Nov 2018 10:19:50 +0900 Subject: [PATCH 003/315] Fix copied todo notes --- Neuron/Services/Common/TransactionService.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neuron/Services/Common/TransactionService.swift b/Neuron/Services/Common/TransactionService.swift index 5a6caac6..972d97b2 100644 --- a/Neuron/Services/Common/TransactionService.swift +++ b/Neuron/Services/Common/TransactionService.swift @@ -176,7 +176,7 @@ extension TransactionService { // TODO: estimate gas let txhash = try sender.sendToken( to: toAddress, - amount: String(format: "%.18lf", amount), // TODO: Inject web3 instance instead of passing password. + amount: String(format: "%.18lf", amount), // TODO: Fix this. Use BigUInt!!! gasLimit: gasLimit, gasPrice: BigUInt(gasPrice), contractAddress: token.address, @@ -212,7 +212,7 @@ extension TransactionService { to: toAddress, quota: BigUInt(UInt(gasLimit)), data: extraData, - value: String(format: "%.18lf", amount), // TODO: Inject web3 instance instead of passing password. + value: String(format: "%.18lf", amount), // TODO: Fix this. Use BigUInt!!! chainId: BigUInt(token.chainId)!, password: password ) From 8da79ef9379249f2072c1bef48b7ddaab94686df Mon Sep 17 00:00:00 2001 From: James Chen Date: Sun, 11 Nov 2018 10:40:54 +0900 Subject: [PATCH 004/315] Start refactoring transaction (params) builder --- Neuron/Sections/Dapp/ContractController.swift | 2 +- .../TransactionGasPriceViewController.swift | 2 +- Neuron/Services/Common/TransactionService.swift | 13 +++++++++---- Neuron/Services/Ethereum/EthereumTxSender.swift | 11 ++++------- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Neuron/Sections/Dapp/ContractController.swift b/Neuron/Sections/Dapp/ContractController.swift index 3b9549ca..cbd21f76 100644 --- a/Neuron/Sections/Dapp/ContractController.swift +++ b/Neuron/Sections/Dapp/ContractController.swift @@ -190,7 +190,7 @@ class ContractController: UITableViewController { service.extraData = Data.init(hex: dappCommonModel.appChain?.data ?? "") service.gasPrice = BigUInt(dappCommonModel.appChain?.quota.clean ?? "")?.words.first ?? 1000000 case .eth: - service.gasLimit = gasLimit.words.first! + service.gasLimit = UInt64(gasLimit.words.first!) service.toAddress = dappCommonModel.eth?.to ?? "" service.extraData = Data.init(hex: dappCommonModel.eth?.data ?? "") service.gasPrice = gasPrice.words.first! diff --git a/Neuron/Sections/Transaction/TransactionGasPriceViewController.swift b/Neuron/Sections/Transaction/TransactionGasPriceViewController.swift index 40724dbe..9a7b2997 100644 --- a/Neuron/Sections/Transaction/TransactionGasPriceViewController.swift +++ b/Neuron/Sections/Transaction/TransactionGasPriceViewController.swift @@ -62,7 +62,7 @@ class TransactionGasPriceViewController: UIViewController { return } service.gasPrice = newGasPrice - service.gasLimit = UInt(gasLimitTextField.text!) ?? 0 + service.gasLimit = UInt64(gasLimitTextField.text!) ?? 0 dismiss() } } diff --git a/Neuron/Services/Common/TransactionService.swift b/Neuron/Services/Common/TransactionService.swift index d2b8c282..17efd767 100644 --- a/Neuron/Services/Common/TransactionService.swift +++ b/Neuron/Services/Common/TransactionService.swift @@ -30,13 +30,13 @@ class TransactionService { var tokenBalance: Double = 0.0 var gasPrice: UInt = 1 { didSet { - let result = Web3Utils.formatToEthereumUnits(BigUInt(gasLimit * gasPrice), toUnits: .eth, decimals: 10) ?? "" + let result = Web3Utils.formatToEthereumUnits(BigUInt(gasLimit) * BigUInt(gasPrice), toUnits: .eth, decimals: 10) ?? "" gasCost = Double(result) ?? 0.0 } } - var gasLimit: UInt = 0 { + var gasLimit: UInt64 = 0 { didSet { - let result = Web3Utils.formatToEthereumUnits(BigUInt(gasLimit * gasPrice), toUnits: .eth, decimals: 10) ?? "" + let result = Web3Utils.formatToEthereumUnits(BigUInt(gasLimit) * BigUInt(gasPrice), toUnits: .eth, decimals: 10) ?? "" gasCost = Double(result) ?? 0.0 } } @@ -138,10 +138,15 @@ extension TransactionService { web3.addKeystoreManager(KeystoreManager([keystore])) do { + // TODO: change amount to string which matches UI input exactly. + guard let value = Web3.Utils.parseToBigUInt(String(amount), units: .eth) else { + throw SendTransactionError.invalidAmountFormat + } + let sender = EthereumTxSender(web3: web3, from: fromAddress) let txhash = try sender.sendETH( to: toAddress, - amount: String(format: "%.18lf", amount), + value: value, gasLimit: gasLimit, gasPrice: BigUInt(gasPrice), data: extraData, diff --git a/Neuron/Services/Ethereum/EthereumTxSender.swift b/Neuron/Services/Ethereum/EthereumTxSender.swift index b0f4f9b9..0486b325 100644 --- a/Neuron/Services/Ethereum/EthereumTxSender.swift +++ b/Neuron/Services/Ethereum/EthereumTxSender.swift @@ -20,10 +20,11 @@ class EthereumTxSender { self.from = from } + /// All parameters should be final, e.g., value should be 10**18 for 1.0 Ether, gasPrice should be 10**9 for 1Gwei. func sendETH( to: String, - amount: String, - gasLimit: UInt = 21000, + value: BigUInt, + gasLimit: UInt64 = 21_000, gasPrice: BigUInt, data: Data, password: String @@ -32,10 +33,6 @@ class EthereumTxSender { throw SendTransactionError.invalidDestinationAddress } - guard let value = Web3.Utils.parseToBigUInt(amount, units: .eth) else { - throw SendTransactionError.invalidAmountFormat - } - guard let contract = web3.contract(Web3.Utils.coldWalletABI, at: toAddress, abiVersion: 2) else { throw SendTransactionError.contractLoadingError } @@ -55,7 +52,7 @@ class EthereumTxSender { func sendToken( to: String, amount: String, - gasLimit: UInt = 21000, + gasLimit: UInt64 = 21_000, gasPrice: BigUInt, contractAddress: String, password: String From b0c9a756916ec7e22c9c03dae93ca051f444bb84 Mon Sep 17 00:00:00 2001 From: James Chen Date: Sun, 11 Nov 2018 19:33:19 +0900 Subject: [PATCH 005/315] Reduce send ETH logic complexity --- Neuron/Services/Common/ServiceErrors.swift | 1 + .../Services/Common/TransactionService.swift | 4 +- .../Services/Ethereum/EthereumTxSender.swift | 59 ++++++++----------- 3 files changed, 28 insertions(+), 36 deletions(-) diff --git a/Neuron/Services/Common/ServiceErrors.swift b/Neuron/Services/Common/ServiceErrors.swift index 3d11735b..8f2dd776 100644 --- a/Neuron/Services/Common/ServiceErrors.swift +++ b/Neuron/Services/Common/ServiceErrors.swift @@ -31,6 +31,7 @@ enum SignMessageResult { } enum SendTransactionError: Error { + case invalidSourceAddress case invalidDestinationAddress case invalidAmountFormat case contractLoadingError diff --git a/Neuron/Services/Common/TransactionService.swift b/Neuron/Services/Common/TransactionService.swift index 17efd767..0d9d3d39 100644 --- a/Neuron/Services/Common/TransactionService.swift +++ b/Neuron/Services/Common/TransactionService.swift @@ -143,7 +143,7 @@ extension TransactionService { throw SendTransactionError.invalidAmountFormat } - let sender = EthereumTxSender(web3: web3, from: fromAddress) + let sender = try EthereumTxSender(web3: web3, from: fromAddress) let txhash = try sender.sendETH( to: toAddress, value: value, @@ -177,7 +177,7 @@ extension TransactionService { web3.addKeystoreManager(KeystoreManager([keystore])) do { - let sender = EthereumTxSender(web3: web3, from: fromAddress) + let sender = try EthereumTxSender(web3: web3, from: fromAddress) // TODO: estimate gas let txhash = try sender.sendToken( to: toAddress, diff --git a/Neuron/Services/Ethereum/EthereumTxSender.swift b/Neuron/Services/Ethereum/EthereumTxSender.swift index 0486b325..5a4c012d 100644 --- a/Neuron/Services/Ethereum/EthereumTxSender.swift +++ b/Neuron/Services/Ethereum/EthereumTxSender.swift @@ -13,11 +13,14 @@ import Web3swift class EthereumTxSender { private let web3: web3 - private let from: String + private let from: EthereumAddress - init(web3: web3, from: String) { + init(web3: web3, from: String) throws { self.web3 = web3 - self.from = from + guard let fromAddress = EthereumAddress(from) else { + throw SendTransactionError.invalidSourceAddress + } + self.from = fromAddress } /// All parameters should be final, e.g., value should be 10**18 for 1.0 Ether, gasPrice should be 10**9 for 1Gwei. @@ -33,20 +36,16 @@ class EthereumTxSender { throw SendTransactionError.invalidDestinationAddress } - guard let contract = web3.contract(Web3.Utils.coldWalletABI, at: toAddress, abiVersion: 2) else { - throw SendTransactionError.contractLoadingError - } + var options = TransactionOptions() + options.gasLimit = .manual(BigUInt(gasLimit)) + options.gasPrice = .manual(gasPrice) + options.from = from - guard let transaction = contract.write("fallback") else { + guard let transaction = web3.eth.sendETH(to: toAddress, amount: value, extraData: data, transactionOptions: options) else { throw SendTransactionError.createTransactionIssue } - transaction.transactionOptions.gasLimit = .manual(BigUInt(gasLimit)) - transaction.transactionOptions.gasPrice = .manual(gasPrice) - transaction.transactionOptions.from = EthereumAddress(from) - transaction.transaction.value = value - let result = try transaction.sendPromise(password: password).wait() - return result.hash + return try transaction.sendPromise(password: password).wait().hash } func sendToken( @@ -61,33 +60,25 @@ class EthereumTxSender { throw SendTransactionError.invalidDestinationAddress } + guard let tokenAddress = EthereumAddress(contractAddress) else { + throw SendTransactionError.createTransactionIssue + } + guard Web3.Utils.parseToBigUInt(amount, units: .eth) != nil else { throw SendTransactionError.invalidAmountFormat } - guard let tokenAddress = EthereumAddress(contractAddress), let fromAddress = EthereumAddress(from) else { + guard let transaction = try web3.eth.sendERC20tokensWithNaturalUnits( + tokenAddress: tokenAddress, + from: from, + to: destinationEthAddress, + amount: amount + ) else { throw SendTransactionError.createTransactionIssue } + transaction.transactionOptions.gasLimit = .manual(BigUInt(gasLimit)) + transaction.transactionOptions.gasPrice = .manual(gasPrice) - var options = Web3Options.defaultOptions() - options.gasLimit = BigUInt(gasLimit) - options.gasPrice = gasPrice - options.from = fromAddress - - do { - guard let transaction = try web3.eth.sendERC20tokensWithNaturalUnits( - tokenAddress: tokenAddress, - from: fromAddress, - to: destinationEthAddress, - amount: amount - ) else { - throw SendTransactionError.createTransactionIssue - } - - let result = try transaction.sendPromise(password: password, transactionOptions: nil).wait() - return result.hash - } catch { - throw SendTransactionError.createTransactionIssue - } + return try transaction.sendPromise(password: password).wait().hash } } From fb19a41df70bcc812d31929a7d1e7ff7ee4853f0 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 12 Nov 2018 11:51:36 +0800 Subject: [PATCH 006/315] Add `PageControl` to guide page --- Neuron/Sections/Guide/Guide.storyboard | 42 +++++++++++++++---- .../Sections/Guide/GuideViewController.swift | 6 +++ 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/Neuron/Sections/Guide/Guide.storyboard b/Neuron/Sections/Guide/Guide.storyboard index 18c232c6..0e11f8f2 100644 --- a/Neuron/Sections/Guide/Guide.storyboard +++ b/Neuron/Sections/Guide/Guide.storyboard @@ -1,11 +1,11 @@ - + - + @@ -69,10 +69,20 @@ - - + + + + + + + + + + + + - + diff --git a/Neuron/Sections/Guide/GuideViewController.swift b/Neuron/Sections/Guide/GuideViewController.swift index fd5823d6..28c05213 100644 --- a/Neuron/Sections/Guide/GuideViewController.swift +++ b/Neuron/Sections/Guide/GuideViewController.swift @@ -24,6 +24,7 @@ class GuideViewController: UIViewController, UICollectionViewDataSource, UIColle ] @IBOutlet weak var collectionView: UICollectionView! + @IBOutlet weak var pageControl: UIPageControl! override func viewDidLoad() { super.viewDidLoad() @@ -52,6 +53,7 @@ class GuideViewController: UIViewController, UICollectionViewDataSource, UIColle // MARK: - GuideViewControllerProtocol func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + pageControl.numberOfPages = items.count return items.count } @@ -64,6 +66,10 @@ class GuideViewController: UIViewController, UICollectionViewDataSource, UIColle return cell } + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + pageControl.currentPage = indexPath.row + } + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return collectionView.bounds.size } From 0247330378c833a01b1293fe9d1fc1406ab84e0a Mon Sep 17 00:00:00 2001 From: LuFP Date: Mon, 12 Nov 2018 12:10:46 +0800 Subject: [PATCH 007/315] Fix navigationBar translucent --- Neuron.xcodeproj/project.pbxproj | 4 - .../View/GenerateMnemonicController.swift | 1 + .../View/GenerateMnemonicController.xib | 142 ------------------ 3 files changed, 1 insertion(+), 146 deletions(-) delete mode 100644 Neuron/Sections/Wallet/View/GenerateMnemonicController.xib diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 0d4f9b2a..aa572a3e 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -95,7 +95,6 @@ 8964A18720BFE6860086848F /* ImportWalletController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A18520BFE6850086848F /* ImportWalletController.swift */; }; 8964A18A20BFEF950086848F /* ImportTextViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A18920BFEF950086848F /* ImportTextViewCell.swift */; }; 8964A18D20C0E4BE0086848F /* GenerateMnemonicController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A18B20C0E4BE0086848F /* GenerateMnemonicController.swift */; }; - 8964A18E20C0E4BE0086848F /* GenerateMnemonicController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8964A18C20C0E4BE0086848F /* GenerateMnemonicController.xib */; }; 8964A19320C115A70086848F /* VerifyMnemonicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A19120C115A70086848F /* VerifyMnemonicViewController.swift */; }; 8964A19620C12FB70086848F /* ButtonTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A19520C12FB70086848F /* ButtonTagView.swift */; }; 8964A19820C15EA20086848F /* ButtonTagUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A19720C15EA20086848F /* ButtonTagUpView.swift */; }; @@ -299,7 +298,6 @@ 8964A18520BFE6850086848F /* ImportWalletController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletController.swift; sourceTree = ""; }; 8964A18920BFEF950086848F /* ImportTextViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportTextViewCell.swift; sourceTree = ""; }; 8964A18B20C0E4BE0086848F /* GenerateMnemonicController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateMnemonicController.swift; sourceTree = ""; }; - 8964A18C20C0E4BE0086848F /* GenerateMnemonicController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GenerateMnemonicController.xib; sourceTree = ""; }; 8964A19120C115A70086848F /* VerifyMnemonicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyMnemonicViewController.swift; sourceTree = ""; }; 8964A19520C12FB70086848F /* ButtonTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonTagView.swift; sourceTree = ""; }; 8964A19720C15EA20086848F /* ButtonTagUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonTagUpView.swift; sourceTree = ""; }; @@ -783,7 +781,6 @@ 898A1A1620B6BE6E00ECB465 /* ChangePasswordController.xib */, 898A1A1920B7A6BB00ECB465 /* CreateWalletController.swift */, 8964A18B20C0E4BE0086848F /* GenerateMnemonicController.swift */, - 8964A18C20C0E4BE0086848F /* GenerateMnemonicController.xib */, 8964A19120C115A70086848F /* VerifyMnemonicViewController.swift */, 8964A18520BFE6850086848F /* ImportWalletController.swift */, 898889C52136521100D04AA8 /* KeystoreViewController.swift */, @@ -1125,7 +1122,6 @@ 8935BA59216EFCE700C37263 /* neuron.js in Resources */, 1A9204E421634212004B54DC /* ToastActivityView.xib in Resources */, 896812CB20F726D000731C8C /* dappOpration.js in Resources */, - 8964A18E20C0E4BE0086848F /* GenerateMnemonicController.xib in Resources */, 89C614572140D4C500DC3DF4 /* currency.plist in Resources */, 898A1A3020B8044900ECB465 /* TradeTableViewCell.xib in Resources */, ); diff --git a/Neuron/Sections/Wallet/View/GenerateMnemonicController.swift b/Neuron/Sections/Wallet/View/GenerateMnemonicController.swift index b2831cb2..e7cec9c8 100644 --- a/Neuron/Sections/Wallet/View/GenerateMnemonicController.swift +++ b/Neuron/Sections/Wallet/View/GenerateMnemonicController.swift @@ -24,6 +24,7 @@ class GenerateMnemonicController: UIViewController, NoScreenshot, EnterBackOverl override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.interactivePopGestureRecognizer?.isEnabled = false + navigationController?.navigationBar.isTranslucent = false setupOverlayBackBarButton() } diff --git a/Neuron/Sections/Wallet/View/GenerateMnemonicController.xib b/Neuron/Sections/Wallet/View/GenerateMnemonicController.xib deleted file mode 100644 index 46825802..00000000 --- a/Neuron/Sections/Wallet/View/GenerateMnemonicController.xib +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From b4c2308ca2dd28e088f84754a2de8c1caa63cb0e Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 12 Nov 2018 14:09:55 +0900 Subject: [PATCH 008/315] Chenge tx sender params --- .../Services/AppChain/AppChainTxSender.swift | 36 ++++++++----------- Neuron/Services/Common/ServiceErrors.swift | 1 + .../Services/Common/TransactionService.swift | 20 +++++++---- .../Services/Ethereum/EthereumTxSender.swift | 24 +++++++------ 4 files changed, 41 insertions(+), 40 deletions(-) diff --git a/Neuron/Services/AppChain/AppChainTxSender.swift b/Neuron/Services/AppChain/AppChainTxSender.swift index 66f1ac28..70dbaa4e 100644 --- a/Neuron/Services/AppChain/AppChainTxSender.swift +++ b/Neuron/Services/AppChain/AppChainTxSender.swift @@ -1,5 +1,5 @@ // -// AppChainTransactionService.swift +// AppChainTxSender.swift // Neuron // // Created by James Chen on 2018/11/06. @@ -13,28 +13,28 @@ import BigInt class AppChainTxSender { private let appChain: AppChain private let walletManager: WalletManager - private let from: String + private let from: Address - init(appChain: AppChain, walletManager: WalletManager, from: String) { + init(appChain: AppChain, walletManager: WalletManager, from: String) throws { self.appChain = appChain self.walletManager = walletManager - self.from = from + guard let fromAddress = Address(from) else { + throw SendTransactionError.invalidSourceAddress + } + self.from = fromAddress } func send( to: String, - quota: BigUInt = BigUInt(21_000), + value: BigUInt, + quota: UInt64 = 21_000, data: Data, - value: String, chainId: BigUInt, password: String ) throws -> TxHash { guard let destinationEthAddress = Address(to) else { throw SendTransactionError.invalidDestinationAddress } - guard let amount = Web3Utils.parseToBigUInt(value, units: .eth) else { - throw SendTransactionError.invalidAmountFormat - } let nonce = UUID().uuidString let appChain = AppChainNetwork.appChain() @@ -44,32 +44,24 @@ class AppChainTxSender { let transaction = Transaction( to: destinationEthAddress, nonce: nonce, - quota: UInt64(quota), + quota: quota, validUntilBlock: blockNumber + UInt64(88), data: data, - value: amount, + value: value, chainId: chainId.description, version: UInt32(0) ) let signed = try sign(transaction: transaction, password: password) - do { - return try appChain.rpc.sendRawTransaction(signedTx: signed) - } catch { - throw SendTransactionError.signTXFailed - } + return try appChain.rpc.sendRawTransaction(signedTx: signed) } func sendToken(transaction: Transaction, password: String) throws -> TxHash { let signed = try sign(transaction: transaction, password: password) - do { - return try appChain.rpc.sendRawTransaction(signedTx: signed) - } catch { - throw SendTransactionError.signTXFailed - } + return try appChain.rpc.sendRawTransaction(signedTx: signed) } func sign(transaction: Transaction, password: String) throws -> String { - guard let wallet = walletManager.wallet(for: from) else { + guard let wallet = walletManager.wallet(for: from.address) else { throw SendTransactionError.noAvailableKeys } let privateKey = try walletManager.exportPrivateKey(wallet: wallet, password: password) diff --git a/Neuron/Services/Common/ServiceErrors.swift b/Neuron/Services/Common/ServiceErrors.swift index 8f2dd776..fabbc8b6 100644 --- a/Neuron/Services/Common/ServiceErrors.swift +++ b/Neuron/Services/Common/ServiceErrors.swift @@ -33,6 +33,7 @@ enum SignMessageResult { enum SendTransactionError: Error { case invalidSourceAddress case invalidDestinationAddress + case invalidContractAddress case invalidAmountFormat case contractLoadingError case retrievingGasPriceError diff --git a/Neuron/Services/Common/TransactionService.swift b/Neuron/Services/Common/TransactionService.swift index 6ed4b15f..9a26009e 100644 --- a/Neuron/Services/Common/TransactionService.swift +++ b/Neuron/Services/Common/TransactionService.swift @@ -52,7 +52,7 @@ class TransactionService { var changeGasPriceEnable = false var isSupportGasSetting: Bool { return changeGasPriceEnable || changeGasLimitEnable } var toAddress = "" - var amount = 0.0 + var amount = 0.0 // Change to BigUInt representing final value (smallet unit, e.g., wei). var extraData = Data() var password: String = "" var isUseQRCode = false // TODO: Fix spelling. @@ -171,17 +171,21 @@ extension TransactionService { } override func sendTransaction() { - // TODO: extract this let keystore = WalletManager.default.keystore(for: fromAddress) let web3 = EthereumNetwork().getWeb3() web3.addKeystoreManager(KeystoreManager([keystore])) do { + // TODO: Get token decimal and convert + guard let value = Web3.Utils.parseToBigUInt(String(amount), units: .eth) else { + throw SendTransactionError.invalidAmountFormat + } + let sender = try EthereumTxSender(web3: web3, from: fromAddress) // TODO: estimate gas let txhash = try sender.sendToken( to: toAddress, - amount: String(format: "%.18lf", amount), // TODO: Fix this. Use BigUInt!!! + value: value, gasLimit: gasLimit, gasPrice: BigUInt(gasPrice), contractAddress: token.address, @@ -206,18 +210,20 @@ extension TransactionService { } override func sendTransaction() { - // TODO: queue async super.sendTransaction() do { guard let appChainUrl = URL(string: token.chainHosts) else { throw SendTransactionError.invalidAppChainNode } - let sender = AppChainTxSender(appChain: AppChainNetwork.appChain(url: appChainUrl), walletManager: WalletManager.default, from: fromAddress) + guard let value = Web3Utils.parseToBigUInt(String(amount), units: .eth) else { + throw SendTransactionError.invalidAmountFormat + } + let sender = try AppChainTxSender(appChain: AppChainNetwork.appChain(url: appChainUrl), walletManager: WalletManager.default, from: fromAddress) let txhash = try sender.send( to: toAddress, - quota: BigUInt(UInt(gasLimit)), + value: value, + quota: gasLimit, data: extraData, - value: String(format: "%.18lf", amount), // TODO: Fix this. Use BigUInt!!! chainId: BigUInt(token.chainId)!, password: password ) diff --git a/Neuron/Services/Ethereum/EthereumTxSender.swift b/Neuron/Services/Ethereum/EthereumTxSender.swift index 5a4c012d..7c2cb5ce 100644 --- a/Neuron/Services/Ethereum/EthereumTxSender.swift +++ b/Neuron/Services/Ethereum/EthereumTxSender.swift @@ -41,7 +41,12 @@ class EthereumTxSender { options.gasPrice = .manual(gasPrice) options.from = from - guard let transaction = web3.eth.sendETH(to: toAddress, amount: value, extraData: data, transactionOptions: options) else { + guard let transaction = web3.eth.sendETH( + to: toAddress, + amount: value, + extraData: data, + transactionOptions: options + ) else { throw SendTransactionError.createTransactionIssue } @@ -50,32 +55,29 @@ class EthereumTxSender { func sendToken( to: String, - amount: String, + value: BigUInt, gasLimit: UInt64 = 21_000, gasPrice: BigUInt, contractAddress: String, password: String ) throws -> TxHash { - guard let destinationEthAddress = EthereumAddress(to) else { + guard let destinationAddress = EthereumAddress(to) else { throw SendTransactionError.invalidDestinationAddress } guard let tokenAddress = EthereumAddress(contractAddress) else { - throw SendTransactionError.createTransactionIssue + throw SendTransactionError.invalidContractAddress } - guard Web3.Utils.parseToBigUInt(amount, units: .eth) != nil else { - throw SendTransactionError.invalidAmountFormat - } - - guard let transaction = try web3.eth.sendERC20tokensWithNaturalUnits( + guard let transaction = web3.eth.sendERC20tokensWithKnownDecimals( tokenAddress: tokenAddress, from: from, - to: destinationEthAddress, - amount: amount + to: destinationAddress, + amount: value ) else { throw SendTransactionError.createTransactionIssue } + transaction.transactionOptions.gasLimit = .manual(BigUInt(gasLimit)) transaction.transactionOptions.gasPrice = .manual(gasPrice) From 54a51a7241a0c264367bdf2fc3b368660fb19e94 Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 12 Nov 2018 14:28:40 +0900 Subject: [PATCH 009/315] Rename TransactionService to TransactionParamBuilder and remove password as its property --- Neuron.xcodeproj/project.pbxproj | 8 ++-- Neuron/Sections/Dapp/ContractController.swift | 8 ++-- .../TransactionConfirmViewController.swift | 7 ++- .../TransactionGasPriceViewController.swift | 2 +- .../TransactionParamBuilder.swift} | 45 +++++++++---------- .../TransactionViewController.swift | 12 ++--- Neuron/Utils/Web3Utils.swift | 2 + 7 files changed, 41 insertions(+), 43 deletions(-) rename Neuron/{Services/Common/TransactionService.swift => Sections/Transaction/TransactionParamBuilder.swift} (88%) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index d4cccda7..8d3d7495 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -32,7 +32,7 @@ 1AB3D1CF21709F6700557E9D /* UIStoryboard+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB3D1CE21709F6700557E9D /* UIStoryboard+Extension.swift */; }; 1AB3D1D221709F9600557E9D /* UIStoryboardExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AB3D1D121709F9600557E9D /* UIStoryboardExtensionTests.swift */; }; 3B9D2E9C9E858D6634948E6D /* Pods_Neuron.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8CC6FD685582D038A3767657 /* Pods_Neuron.framework */; }; - 6E4F9618218973F200372D3E /* TransactionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4F9617218973F200372D3E /* TransactionService.swift */; }; + 6E4F9618218973F200372D3E /* TransactionParamBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4F9617218973F200372D3E /* TransactionParamBuilder.swift */; }; 6E4F961B2189A82900372D3E /* TransactionConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4F961A2189A82900372D3E /* TransactionConfirmViewController.swift */; }; 6E8168F7218874B1007641BA /* TransactionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8168F6218874B1007641BA /* TransactionViewController.swift */; }; 6E8168F921887A31007641BA /* TransactionGasPriceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8168F821887A31007641BA /* TransactionGasPriceViewController.swift */; }; @@ -234,7 +234,7 @@ 1D270F090888C6AD9CF6A055 /* Pods-Neuron.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Neuron.release.xcconfig"; path = "Pods/Target Support Files/Pods-Neuron/Pods-Neuron.release.xcconfig"; sourceTree = ""; }; 21C5F1D714208410CA8D77B9 /* Pods-Neuron.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Neuron.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Neuron/Pods-Neuron.debug.xcconfig"; sourceTree = ""; }; 3502E1744CFA926363F980D0 /* Pods_NeuronUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NeuronUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 6E4F9617218973F200372D3E /* TransactionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionService.swift; sourceTree = ""; }; + 6E4F9617218973F200372D3E /* TransactionParamBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionParamBuilder.swift; sourceTree = ""; }; 6E4F961A2189A82900372D3E /* TransactionConfirmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionConfirmViewController.swift; sourceTree = ""; }; 6E8168F6218874B1007641BA /* TransactionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionViewController.swift; sourceTree = ""; }; 6E8168F821887A31007641BA /* TransactionGasPriceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionGasPriceViewController.swift; sourceTree = ""; }; @@ -900,6 +900,7 @@ 898A1A1E20B7B4F300ECB465 /* View */, 89F79670213BD8C40064808A /* Transaction.storyboard */, 89C6145F2141421400DC3DF4 /* TransactionHistoryViewController.swift */, + 6E4F9617218973F200372D3E /* TransactionParamBuilder.swift */, 6E8168F6218874B1007641BA /* TransactionViewController.swift */, 6E8168F821887A31007641BA /* TransactionGasPriceViewController.swift */, 6E4F961A2189A82900372D3E /* TransactionConfirmViewController.swift */, @@ -931,7 +932,6 @@ isa = PBXGroup; children = ( 89FE21DD20EDB70000A09302 /* ServiceErrors.swift */, - 6E4F9617218973F200372D3E /* TransactionService.swift */, 89D4AC3520D8AFC50097E02B /* WalletRealmTool.swift */, 6E876842218023520032EBCE /* CoinMarketCap.swift */, 89CA1FAD213F832800669B2C /* CurrencyService.swift */, @@ -1336,7 +1336,7 @@ 898A1A2620B7DAA300ECB465 /* TransactionTableviewCell.swift in Sources */, 897AFC5B2130129500383A12 /* CurrencyViewController.swift in Sources */, 898889C82136526800D04AA8 /* MnemonicViewController.swift in Sources */, - 6E4F9618218973F200372D3E /* TransactionService.swift in Sources */, + 6E4F9618218973F200372D3E /* TransactionParamBuilder.swift in Sources */, 1A5515E7218D36B600D34791 /* WalletManager+Errors.swift in Sources */, 6E867E0D216C92A900BD6FE5 /* OverlayPresentable.swift in Sources */, 89D84CE121475755006B0287 /* NFTService.swift in Sources */, diff --git a/Neuron/Sections/Dapp/ContractController.swift b/Neuron/Sections/Dapp/ContractController.swift index cbd21f76..aa23abf2 100644 --- a/Neuron/Sections/Dapp/ContractController.swift +++ b/Neuron/Sections/Dapp/ContractController.swift @@ -178,7 +178,7 @@ class ContractController: UITableViewController { } @IBAction func didClickConfirmButton(_ sender: UIButton) { - let service = TransactionService.service(with: tokenModel) + let service = TransactionParamBuilder.service(with: tokenModel) service.fromAddress = WalletRealmTool.getCurrentAppModel().currentWallet!.address service.amount = Double(value) ?? 0.0 service.delegate = self @@ -203,8 +203,8 @@ class ContractController: UITableViewController { } } -extension ContractController: TransactionServiceDelegate { - func transactionCompletion(_ transactionService: TransactionService, result: TransactionService.Result) { +extension ContractController: TransactionParamBuilderDelegate { + func transactionCompletion(_ transactionService: TransactionParamBuilder, result: TransactionParamBuilder.Result) { switch result { case .error: delegate?.callBackWebView(id: dappCommonModel.id, value: "", error: DAppError.sendTransactionFailed) @@ -214,7 +214,7 @@ extension ContractController: TransactionServiceDelegate { confirmViewController?.dismiss() navigationController?.popViewController(animated: true) } - func transactionGasCostChanged(_ transactionService: TransactionService) { + func transactionGasCostChanged(_ transactionService: TransactionParamBuilder) { } } diff --git a/Neuron/Sections/Transaction/TransactionConfirmViewController.swift b/Neuron/Sections/Transaction/TransactionConfirmViewController.swift index d68649b4..644c148b 100644 --- a/Neuron/Sections/Transaction/TransactionConfirmViewController.swift +++ b/Neuron/Sections/Transaction/TransactionConfirmViewController.swift @@ -21,7 +21,7 @@ class TransactionConfirmViewController: UIViewController, TransactionConfirmSend @IBOutlet weak var contentView: UIView! @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var containView: UIView! - var service: TransactionService! { + var service: TransactionParamBuilder! { didSet { let controller: TransactionConfirmInfoViewController = UIStoryboard(name: .transaction).instantiateViewController() controller.service = service @@ -90,11 +90,10 @@ class TransactionConfirmViewController: UIViewController, TransactionConfirmSend func confirmWalletPassword(password: String) { if let service = service { - service.password = password Toast.showHUD() DispatchQueue.global().async { DispatchQueue.main.async { - self.service.sendTransaction() + self.service.sendTransaction(password: password) Toast.hideHUD() } } @@ -148,7 +147,7 @@ class TransactionConfirmInfoViewController: UIViewController { @IBOutlet weak var fromAddressLabel: UILabel! @IBOutlet weak var toAddressLabel: UILabel! @IBOutlet weak var gasCostLabel: UILabel! - var service: TransactionService! { + var service: TransactionParamBuilder! { didSet { _ = view // load view let amount = service.amount diff --git a/Neuron/Sections/Transaction/TransactionGasPriceViewController.swift b/Neuron/Sections/Transaction/TransactionGasPriceViewController.swift index 9a7b2997..30e42f44 100644 --- a/Neuron/Sections/Transaction/TransactionGasPriceViewController.swift +++ b/Neuron/Sections/Transaction/TransactionGasPriceViewController.swift @@ -16,7 +16,7 @@ class TransactionGasPriceViewController: UIViewController { @IBOutlet weak var estimatedGasPriceLabel: UILabel! @IBOutlet weak var gasPriceTextField: UITextField! @IBOutlet weak var gasLimitTextField: UITextField! - var service: TransactionService! + var service: TransactionParamBuilder! override func viewDidLoad() { super.viewDidLoad() diff --git a/Neuron/Services/Common/TransactionService.swift b/Neuron/Sections/Transaction/TransactionParamBuilder.swift similarity index 88% rename from Neuron/Services/Common/TransactionService.swift rename to Neuron/Sections/Transaction/TransactionParamBuilder.swift index 9a26009e..b127f068 100644 --- a/Neuron/Services/Common/TransactionService.swift +++ b/Neuron/Sections/Transaction/TransactionParamBuilder.swift @@ -1,5 +1,5 @@ // -// TransactionService.swift +// TransactionParamBuilder.swift // Neuron // // Created by 晨风 on 2018/10/31. @@ -11,20 +11,18 @@ import AppChain import Web3swift import BigInt -typealias TxHash = String - -protocol TransactionServiceDelegate: NSObjectProtocol { - func transactionCompletion(_ transactionService: TransactionService, result: TransactionService.Result) - func transactionGasCostChanged(_ transactionService: TransactionService) +protocol TransactionParamBuilderDelegate: NSObjectProtocol { + func transactionCompletion(_ transactionService: TransactionParamBuilder, result: TransactionParamBuilder.Result) + func transactionGasCostChanged(_ transactionService: TransactionParamBuilder) } -class TransactionService { +class TransactionParamBuilder { enum Result { case error(Error) case succee(TxHash) } - weak var delegate: TransactionServiceDelegate? + weak var delegate: TransactionParamBuilderDelegate? var token: TokenModel! var fromAddress: String! var tokenBalance: Double = 0.0 @@ -54,7 +52,6 @@ class TransactionService { var toAddress = "" var amount = 0.0 // Change to BigUInt representing final value (smallet unit, e.g., wei). var extraData = Data() - var password: String = "" var isUseQRCode = false // TODO: Fix spelling. var estimatedGasPrice: UInt = 1 { didSet { @@ -67,7 +64,7 @@ class TransactionService { tokenBalance = Double(token.tokenBalance) ?? 0.0 } - static func service(with token: TokenModel) -> TransactionService { + static func service(with token: TokenModel) -> TransactionParamBuilder { if token.type == .erc20 { return ERC20(token: token) } else if token.type == .ethereum { @@ -84,7 +81,7 @@ class TransactionService { func requestGasCost() { } - func sendTransaction() { + func sendTransaction(password: String) { } func completion(result: Result) { @@ -95,7 +92,7 @@ class TransactionService { } // TODO: move this out of Transaction Service. - private func trackEvent(_ result: TransactionService.Result) { + private func trackEvent(_ result: TransactionParamBuilder.Result) { switch result { case .error: if isUseQRCode { @@ -117,8 +114,8 @@ class TransactionService { } } -extension TransactionService { - class Ethereum: TransactionService { +extension TransactionParamBuilder { + class Ethereum: TransactionParamBuilder { override func requestGasCost() { self.gasLimit = 21_000 /* @@ -131,7 +128,7 @@ extension TransactionService { changeGasPriceEnable = false } - override func sendTransaction() { + override func sendTransaction(password: String) { // TODO: extract this let keystore = WalletManager.default.keystore(for: fromAddress) let web3 = EthereumNetwork().getWeb3() @@ -160,8 +157,8 @@ extension TransactionService { } } -extension TransactionService { - class ERC20: TransactionService { +extension TransactionParamBuilder { + class ERC20: TransactionParamBuilder { override func requestGasCost() { self.gasLimit = 21_000 let bigNumber = try? EthereumNetwork().getWeb3().eth.getGasPrice() @@ -170,7 +167,7 @@ extension TransactionService { changeGasPriceEnable = false } - override func sendTransaction() { + override func sendTransaction(password: String) { let keystore = WalletManager.default.keystore(for: fromAddress) let web3 = EthereumNetwork().getWeb3() web3.addKeystoreManager(KeystoreManager([keystore])) @@ -199,8 +196,8 @@ extension TransactionService { } } -extension TransactionService { - class AppChain: TransactionService { +extension TransactionParamBuilder { + class AppChain: TransactionParamBuilder { override func requestGasCost() { self.gasLimit = 21_000 let quotaPrice = try? Utils.getQuotaPrice(appChain: AppChainNetwork.appChain()) @@ -209,8 +206,8 @@ extension TransactionService { changeGasPriceEnable = false } - override func sendTransaction() { - super.sendTransaction() + override func sendTransaction(password: String) { + super.sendTransaction(password: password) do { guard let appChainUrl = URL(string: token.chainHosts) else { throw SendTransactionError.invalidAppChainNode @@ -235,8 +232,8 @@ extension TransactionService { } } -extension TransactionService { - class AppChainERC20: TransactionService { +extension TransactionParamBuilder { + class AppChainERC20: TransactionParamBuilder { override func requestGasCost() { self.gasLimit = 100_000 let bigNumber = try? Utils.getQuotaPrice(appChain: AppChainNetwork.appChain()) diff --git a/Neuron/Sections/Transaction/TransactionViewController.swift b/Neuron/Sections/Transaction/TransactionViewController.swift index c0924a16..197c1161 100644 --- a/Neuron/Sections/Transaction/TransactionViewController.swift +++ b/Neuron/Sections/Transaction/TransactionViewController.swift @@ -10,7 +10,7 @@ import UIKit import Web3swift import EthereumAddress -class TransactionViewController: UITableViewController, TransactionServiceDelegate { +class TransactionViewController: UITableViewController, TransactionParamBuilderDelegate { // Wallet @IBOutlet weak var walletIconView: UIImageView! @IBOutlet weak var walletNameLabel: UILabel! @@ -19,13 +19,13 @@ class TransactionViewController: UITableViewController, TransactionServiceDelega @IBOutlet weak var amountTextField: UITextField! @IBOutlet weak var gasCostLabel: UILabel! @IBOutlet weak var addressTextField: UITextField! - var service: TransactionService! + var service: TransactionParamBuilder! var token: TokenModel! var confirmViewController: TransactionConfirmViewController? override func viewDidLoad() { super.viewDidLoad() - service = TransactionService.service(with: token) + service = TransactionParamBuilder.service(with: token) service.fromAddress = WalletRealmTool.getCurrentAppModel().currentWallet!.address service.delegate = self Toast.showHUD() @@ -75,8 +75,8 @@ class TransactionViewController: UITableViewController, TransactionServiceDelega amountTextField.text = "\(amount)" } - // MARK: - TransactionServiceDelegate - func transactionCompletion(_ transactionService: TransactionService, result: TransactionService.Result) { + // MARK: - TransactionParamBuilderDelegate + func transactionCompletion(_ transactionService: TransactionParamBuilder, result: TransactionParamBuilder.Result) { switch result { case .error(let error): Toast.showToast(text: error.localizedDescription) @@ -87,7 +87,7 @@ class TransactionViewController: UITableViewController, TransactionServiceDelega } } - func transactionGasCostChanged(_ transactionService: TransactionService) { + func transactionGasCostChanged(_ transactionService: TransactionParamBuilder) { gasCostLabel.text = "\(service.gasCost.clean)\(token.gasSymbol)" } diff --git a/Neuron/Utils/Web3Utils.swift b/Neuron/Utils/Web3Utils.swift index 7a516d0e..5abd231b 100644 --- a/Neuron/Utils/Web3Utils.swift +++ b/Neuron/Utils/Web3Utils.swift @@ -10,3 +10,5 @@ import Foundation import Web3swift typealias Web3Utils = Web3swift.Web3Utils + +typealias TxHash = String From 0e2b393d181484d89d1c34c916300f384fa11953 Mon Sep 17 00:00:00 2001 From: LuFP Date: Mon, 12 Nov 2018 14:53:23 +0800 Subject: [PATCH 010/315] Fix import wallet warn text position and content --- Neuron.xcodeproj/project.pbxproj | 4 - .../Sections/Wallet/View/AddWallet.storyboard | 58 ++++--- .../View/ExportKeystoreController.swift | 4 +- .../View/GenerateMnemonicController.xib | 142 ------------------ .../Wallet/View/MnemonicViewController.swift | 1 - 5 files changed, 36 insertions(+), 173 deletions(-) delete mode 100644 Neuron/Sections/Wallet/View/GenerateMnemonicController.xib diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index d4cccda7..ada69136 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -95,7 +95,6 @@ 8964A18720BFE6860086848F /* ImportWalletController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A18520BFE6850086848F /* ImportWalletController.swift */; }; 8964A18A20BFEF950086848F /* ImportTextViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A18920BFEF950086848F /* ImportTextViewCell.swift */; }; 8964A18D20C0E4BE0086848F /* GenerateMnemonicController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A18B20C0E4BE0086848F /* GenerateMnemonicController.swift */; }; - 8964A18E20C0E4BE0086848F /* GenerateMnemonicController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8964A18C20C0E4BE0086848F /* GenerateMnemonicController.xib */; }; 8964A19320C115A70086848F /* VerifyMnemonicViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A19120C115A70086848F /* VerifyMnemonicViewController.swift */; }; 8964A19620C12FB70086848F /* ButtonTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A19520C12FB70086848F /* ButtonTagView.swift */; }; 8964A19820C15EA20086848F /* ButtonTagUpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A19720C15EA20086848F /* ButtonTagUpView.swift */; }; @@ -299,7 +298,6 @@ 8964A18520BFE6850086848F /* ImportWalletController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletController.swift; sourceTree = ""; }; 8964A18920BFEF950086848F /* ImportTextViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportTextViewCell.swift; sourceTree = ""; }; 8964A18B20C0E4BE0086848F /* GenerateMnemonicController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateMnemonicController.swift; sourceTree = ""; }; - 8964A18C20C0E4BE0086848F /* GenerateMnemonicController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = GenerateMnemonicController.xib; sourceTree = ""; }; 8964A19120C115A70086848F /* VerifyMnemonicViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifyMnemonicViewController.swift; sourceTree = ""; }; 8964A19520C12FB70086848F /* ButtonTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonTagView.swift; sourceTree = ""; }; 8964A19720C15EA20086848F /* ButtonTagUpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonTagUpView.swift; sourceTree = ""; }; @@ -783,7 +781,6 @@ 898A1A1620B6BE6E00ECB465 /* ChangePasswordController.xib */, 898A1A1920B7A6BB00ECB465 /* CreateWalletController.swift */, 8964A18B20C0E4BE0086848F /* GenerateMnemonicController.swift */, - 8964A18C20C0E4BE0086848F /* GenerateMnemonicController.xib */, 8964A19120C115A70086848F /* VerifyMnemonicViewController.swift */, 8964A18520BFE6850086848F /* ImportWalletController.swift */, 898889C52136521100D04AA8 /* KeystoreViewController.swift */, @@ -1125,7 +1122,6 @@ 8935BA59216EFCE700C37263 /* neuron.js in Resources */, 1A9204E421634212004B54DC /* ToastActivityView.xib in Resources */, 896812CB20F726D000731C8C /* dappOpration.js in Resources */, - 8964A18E20C0E4BE0086848F /* GenerateMnemonicController.xib in Resources */, 89C614572140D4C500DC3DF4 /* currency.plist in Resources */, 898A1A3020B8044900ECB465 /* TradeTableViewCell.xib in Resources */, ); diff --git a/Neuron/Sections/Wallet/View/AddWallet.storyboard b/Neuron/Sections/Wallet/View/AddWallet.storyboard index 7d20a1e6..0951f67b 100644 --- a/Neuron/Sections/Wallet/View/AddWallet.storyboard +++ b/Neuron/Sections/Wallet/View/AddWallet.storyboard @@ -203,13 +203,15 @@ - - - + + + + + + - + + + + + + + + @@ -370,7 +380,7 @@ - + - - + + - - + + + - @@ -584,7 +594,7 @@ - + - - + + + - - - + + @@ -796,7 +806,7 @@ - + @@ -812,9 +822,9 @@ - + @@ -1113,7 +1123,7 @@ - + @@ -1134,7 +1144,7 @@ - + diff --git a/Neuron/Sections/Wallet/View/ExportKeystoreController.swift b/Neuron/Sections/Wallet/View/ExportKeystoreController.swift index b1fd5406..4786a2cd 100644 --- a/Neuron/Sections/Wallet/View/ExportKeystoreController.swift +++ b/Neuron/Sections/Wallet/View/ExportKeystoreController.swift @@ -17,7 +17,7 @@ class ExportKeystoreController: UIViewController, EnterBackOverlayPresentable { override func viewDidLoad() { super.viewDidLoad() - title = "导出keystore" + title = "导出Keystore" walletModel = WalletRealmTool.getCurrentAppModel().currentWallet! kestoreTextView.text = keystoreString setUpUI() @@ -32,7 +32,7 @@ class ExportKeystoreController: UIViewController, EnterBackOverlayPresentable { @IBAction func didClickCopyButton(_ sender: UIButton) { UIPasteboard.general.string = keystoreString - Toast.showToast(text: "keystore已经复制到粘贴板") + Toast.showToast(text: "Keystore已经复制到粘贴板") } @IBAction func didClickShareButton(_ sender: UIButton) { diff --git a/Neuron/Sections/Wallet/View/GenerateMnemonicController.xib b/Neuron/Sections/Wallet/View/GenerateMnemonicController.xib deleted file mode 100644 index 46825802..00000000 --- a/Neuron/Sections/Wallet/View/GenerateMnemonicController.xib +++ /dev/null @@ -1,142 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Neuron/Sections/Wallet/View/MnemonicViewController.swift b/Neuron/Sections/Wallet/View/MnemonicViewController.swift index dd74bbea..4f74714e 100644 --- a/Neuron/Sections/Wallet/View/MnemonicViewController.swift +++ b/Neuron/Sections/Wallet/View/MnemonicViewController.swift @@ -22,7 +22,6 @@ class MnemonicViewController: UITableViewController, ImportWalletViewModelDelega override func viewDidLoad() { super.viewDidLoad() - mnemonicTextView.placeholder = "请输入助记词+空格" mnemonicTextView.delegate = self viewModel.delegate = self } From 4640a9d5e607dfbc9e23bb5e02854de6824faabd Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 12 Nov 2018 16:25:55 +0900 Subject: [PATCH 011/315] Remove QR scan event tracking --- .../Transaction/TransactionParamBuilder.swift | 8 +------ .../Wallet/View/AddAssetController.swift | 16 -------------- .../Wallet/View/KeystoreViewController.swift | 2 -- .../Wallet/View/MnemonicViewController.swift | 2 -- .../View/PrivatekeyViewController.swift | 6 +++-- .../ViewModel/ImportWalletViewModel.swift | 22 ------------------- 6 files changed, 5 insertions(+), 51 deletions(-) diff --git a/Neuron/Sections/Transaction/TransactionParamBuilder.swift b/Neuron/Sections/Transaction/TransactionParamBuilder.swift index b127f068..6dd5e3e6 100644 --- a/Neuron/Sections/Transaction/TransactionParamBuilder.swift +++ b/Neuron/Sections/Transaction/TransactionParamBuilder.swift @@ -52,7 +52,6 @@ class TransactionParamBuilder { var toAddress = "" var amount = 0.0 // Change to BigUInt representing final value (smallet unit, e.g., wei). var extraData = Data() - var isUseQRCode = false // TODO: Fix spelling. var estimatedGasPrice: UInt = 1 { didSet { gasPrice = estimatedGasPrice @@ -95,9 +94,7 @@ class TransactionParamBuilder { private func trackEvent(_ result: TransactionParamBuilder.Result) { switch result { case .error: - if isUseQRCode { - SensorsAnalytics.Track.scanQRCode(scanType: .walletAddress, scanResult: false) - } + break default: SensorsAnalytics.Track.transaction( chainType: token.chainId, @@ -107,9 +104,6 @@ class TransactionParamBuilder { outcomeAddress: fromAddress, transactionType: .normal ) - if isUseQRCode { - SensorsAnalytics.Track.scanQRCode(scanType: .walletAddress, scanResult: true) - } } } } diff --git a/Neuron/Sections/Wallet/View/AddAssetController.swift b/Neuron/Sections/Wallet/View/AddAssetController.swift index 9d76299f..e1b471b2 100644 --- a/Neuron/Sections/Wallet/View/AddAssetController.swift +++ b/Neuron/Sections/Wallet/View/AddAssetController.swift @@ -9,7 +9,6 @@ import UIKit class AddAssetController: UIViewController, UITableViewDelegate, UITableViewDataSource, AddAssetTableViewCellDelegate, NEPickerViewDelegate, QRCodeViewControllerDelegate { - let titleArray = ["区块链", "合约地址", "代币名称", "代币缩写", "小数位数"] let placeholderArray = ["", "合约地址", "代币名称", "代币缩写", "小数位数"] @@ -18,7 +17,6 @@ class AddAssetController: UIViewController, UITableViewDelegate, UITableViewData @IBOutlet weak var addButton: UIButton! @IBOutlet weak var aTable: UITableView! var tokenModel = TokenModel() - var isUseQRCode = false override func viewDidLoad() { super.viewDidLoad() @@ -35,9 +33,6 @@ class AddAssetController: UIViewController, UITableViewDelegate, UITableViewData Toast.hideHUD() if tokenModel.address.count != 40 && tokenModel.address.count != 42 { Toast.showToast(text: "请输入正确的合约地址") - if isUseQRCode { - SensorsAnalytics.Track.scanQRCode(scanType: .walletAddress, scanResult: false) - } return } if tokenModel.name.isEmpty || tokenModel.symbol.isEmpty || String(tokenModel.decimals).isEmpty { @@ -55,9 +50,6 @@ class AddAssetController: UIViewController, UITableViewDelegate, UITableViewData WalletRealmTool.realm.add(tokenModel, update: true) appModel.extraTokenList.append(tokenModel) appModel.currentWallet?.selectTokenList.append(tokenModel) - if isUseQRCode { - SensorsAnalytics.Track.scanQRCode(scanType: .walletAddress, scanResult: true) - } } navigationController?.popViewController(animated: true) } @@ -128,7 +120,6 @@ class AddAssetController: UIViewController, UITableViewDelegate, UITableViewData tokenModel.address = "" let finalText = codeResult.replacingOccurrences(of: " ", with: "") tokenModel.address = finalText - isUseQRCode = true if finalText.count == 40 || finalText.count == 42 { didGetERC20Token(token: finalText) } @@ -138,7 +129,6 @@ class AddAssetController: UIViewController, UITableViewDelegate, UITableViewData func didGetTextFieldTextWithIndexAndText(text: String, index: NSIndexPath) { let finalText = text.replacingOccurrences(of: " ", with: "") tokenModel.address = finalText - isUseQRCode = false if index.row == 1 { if finalText.count == 40 || finalText.count == 42 { didGetERC20Token(token: finalText) @@ -156,16 +146,10 @@ class AddAssetController: UIViewController, UITableViewDelegate, UITableViewData case .success(let tokenM): self.tokenModel = tokenM self.tokenModel.address = token - self.isUseQRCode = false case .error: Toast.showToast(text: "未查询到代币信息,请核对合约地址是否正确") } self.aTable.reloadData() } } - - override func didReceiveMemoryWarning() { - super.didReceiveMemoryWarning() - } - } diff --git a/Neuron/Sections/Wallet/View/KeystoreViewController.swift b/Neuron/Sections/Wallet/View/KeystoreViewController.swift index 0332885b..fabcda90 100644 --- a/Neuron/Sections/Wallet/View/KeystoreViewController.swift +++ b/Neuron/Sections/Wallet/View/KeystoreViewController.swift @@ -41,7 +41,6 @@ class KeystoreViewController: UITableViewController, ImportWalletViewModelDelega func didBackQRCodeMessage(codeResult: String) { keystore = codeResult - viewModel.isUseQRCode = true keyStoreTextView.text = codeResult judgeImportButtonEnabled() } @@ -75,7 +74,6 @@ class KeystoreViewController: UITableViewController, ImportWalletViewModelDelega extension KeystoreViewController: UITextViewDelegate { func textViewDidEndEditing(_ textView: UITextView) { keystore = textView.text - viewModel.isUseQRCode = false judgeImportButtonEnabled() } } diff --git a/Neuron/Sections/Wallet/View/MnemonicViewController.swift b/Neuron/Sections/Wallet/View/MnemonicViewController.swift index dd74bbea..f922bd03 100644 --- a/Neuron/Sections/Wallet/View/MnemonicViewController.swift +++ b/Neuron/Sections/Wallet/View/MnemonicViewController.swift @@ -46,13 +46,11 @@ class MnemonicViewController: UITableViewController, ImportWalletViewModelDelega func didGetTextViewText(text: String) { mnemonic = text - viewModel.isUseQRCode = false judgeImportButtonEnabled() } func didBackQRCodeMessage(codeResult: String) { mnemonic = codeResult - viewModel.isUseQRCode = true mnemonicTextView.text = codeResult judgeImportButtonEnabled() } diff --git a/Neuron/Sections/Wallet/View/PrivatekeyViewController.swift b/Neuron/Sections/Wallet/View/PrivatekeyViewController.swift index 843845e4..def55000 100644 --- a/Neuron/Sections/Wallet/View/PrivatekeyViewController.swift +++ b/Neuron/Sections/Wallet/View/PrivatekeyViewController.swift @@ -29,24 +29,26 @@ class PrivatekeyViewController: UITableViewController, ImportWalletViewModelDele name = sender.text judgeImportButtonEnabled() } + @IBAction func passwordChanged(_ sender: UITextField) { sender.text = sender.text?.trimmingCharacters(in: .whitespaces) password = sender.text judgeImportButtonEnabled() } + @IBAction func confirmPasswordChanged(_ sender: UITextField) { sender.text = sender.text?.trimmingCharacters(in: .whitespaces) confirmPassword = sender.text judgeImportButtonEnabled() } + func didGetTextViewText(text: String) { privateKey = text - viewModel.isUseQRCode = false judgeImportButtonEnabled() } + func didBackQRCodeMessage(codeResult: String) { privateKey = codeResult - viewModel.isUseQRCode = true privatekeyTextView.text = codeResult judgeImportButtonEnabled() } diff --git a/Neuron/Sections/Wallet/ViewModel/ImportWalletViewModel.swift b/Neuron/Sections/Wallet/ViewModel/ImportWalletViewModel.swift index b1889213..1d3a6bfb 100644 --- a/Neuron/Sections/Wallet/ViewModel/ImportWalletViewModel.swift +++ b/Neuron/Sections/Wallet/ViewModel/ImportWalletViewModel.swift @@ -26,7 +26,6 @@ class ImportWalletViewModel: NSObject { weak var delegate: ImportWalletViewModelDelegate? var walletModel = WalletModel() var importType = ImportWalletType.keystoreType - var isUseQRCode = false /// if change the way to import wallet the walletModel should be empy func changeImportWay() { @@ -64,18 +63,12 @@ class ImportWalletViewModel: NSObject { self.walletModel.address = EthereumAddress.toChecksumAddress(wallet.address)! self.saveWalletToRealm() SensorsAnalytics.Track.importWallet(type: .keystore, address: self.walletModel.address) - if self.isUseQRCode { - SensorsAnalytics.Track.scanQRCode(scanType: .keystore, scanResult: true) - } } } catch let error { DispatchQueue.main.async { Toast.hideHUD() Toast.showToast(text: error.localizedDescription) SensorsAnalytics.Track.importWallet(type: .keystore, address: nil) - if self.isUseQRCode { - SensorsAnalytics.Track.scanQRCode(scanType: .keystore, scanResult: false) - } } } } @@ -102,9 +95,6 @@ class ImportWalletViewModel: NSObject { } Toast.showToast(text: "导入成功") SensorsAnalytics.Track.importWallet(type: .keystore, address: self.walletModel.address) - if self.isUseQRCode { - SensorsAnalytics.Track.scanQRCode(scanType: .keystore, scanResult: true) - } if isFirstWallet { NotificationCenter.default.post(name: .firstWalletCreated, object: nil) } @@ -152,18 +142,12 @@ class ImportWalletViewModel: NSObject { self.walletModel.address = EthereumAddress.toChecksumAddress(wallet.address)! self.saveWalletToRealm() SensorsAnalytics.Track.importWallet(type: .mnemonic, address: self.walletModel.address) - if self.isUseQRCode { - SensorsAnalytics.Track.scanQRCode(scanType: .mnemonic, scanResult: true) - } } } catch let error { DispatchQueue.main.async { Toast.hideHUD() Toast.showToast(text: error.localizedDescription) SensorsAnalytics.Track.importWallet(type: .mnemonic, address: nil) - if self.isUseQRCode { - SensorsAnalytics.Track.scanQRCode(scanType: .mnemonic, scanResult: false) - } } } } @@ -204,18 +188,12 @@ class ImportWalletViewModel: NSObject { self.walletModel.address = EthereumAddress.toChecksumAddress(wallet.address)! self.saveWalletToRealm() SensorsAnalytics.Track.importWallet(type: .privateKey, address: self.walletModel.address) - if self.isUseQRCode { - SensorsAnalytics.Track.scanQRCode(scanType: .privateKey, scanResult: true) - } } } catch let error { DispatchQueue.main.async { Toast.hideHUD() Toast.showToast(text: error.localizedDescription) SensorsAnalytics.Track.importWallet(type: .privateKey, address: nil) - if self.isUseQRCode { - SensorsAnalytics.Track.scanQRCode(scanType: .privateKey, scanResult: false) - } } } } From 163abbe13115181ab4efe35a06ab8cfb502721d8 Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 12 Nov 2018 16:39:24 +0900 Subject: [PATCH 012/315] Move tx event tracking out of TransactionParamBuilder --- Neuron/Sections/Dapp/ContractController.swift | 8 ++++++++ .../Transaction/TransactionParamBuilder.swift | 18 ------------------ .../TransactionViewController.swift | 9 +++++++++ 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Neuron/Sections/Dapp/ContractController.swift b/Neuron/Sections/Dapp/ContractController.swift index aa23abf2..bb226722 100644 --- a/Neuron/Sections/Dapp/ContractController.swift +++ b/Neuron/Sections/Dapp/ContractController.swift @@ -209,6 +209,14 @@ extension ContractController: TransactionParamBuilderDelegate { case .error: delegate?.callBackWebView(id: dappCommonModel.id, value: "", error: DAppError.sendTransactionFailed) case .succee(let txhash): + SensorsAnalytics.Track.transaction( + chainType: tokenModel.chainId, + currencyType: tokenModel.symbol, + currencyNumber: Double(value) ?? 0.0, + receiveAddress: dappCommonModel.appChain?.to ?? "", + outcomeAddress: WalletRealmTool.getCurrentAppModel().currentWallet!.address, + transactionType: .normal + ) delegate?.callBackWebView(id: dappCommonModel.id, value: txhash.addHexPrefix(), error: nil) } confirmViewController?.dismiss() diff --git a/Neuron/Sections/Transaction/TransactionParamBuilder.swift b/Neuron/Sections/Transaction/TransactionParamBuilder.swift index 6dd5e3e6..e16cf9ee 100644 --- a/Neuron/Sections/Transaction/TransactionParamBuilder.swift +++ b/Neuron/Sections/Transaction/TransactionParamBuilder.swift @@ -86,24 +86,6 @@ class TransactionParamBuilder { func completion(result: Result) { DispatchQueue.main.async { self.delegate?.transactionCompletion(self, result: result) - self.trackEvent(result) - } - } - - // TODO: move this out of Transaction Service. - private func trackEvent(_ result: TransactionParamBuilder.Result) { - switch result { - case .error: - break - default: - SensorsAnalytics.Track.transaction( - chainType: token.chainId, - currencyType: token.symbol, - currencyNumber: amount, - receiveAddress: toAddress, - outcomeAddress: fromAddress, - transactionType: .normal - ) } } } diff --git a/Neuron/Sections/Transaction/TransactionViewController.swift b/Neuron/Sections/Transaction/TransactionViewController.swift index 197c1161..476880fa 100644 --- a/Neuron/Sections/Transaction/TransactionViewController.swift +++ b/Neuron/Sections/Transaction/TransactionViewController.swift @@ -84,6 +84,15 @@ class TransactionViewController: UITableViewController, TransactionParamBuilderD Toast.showToast(text: "转账成功,请稍后刷新查看") confirmViewController?.dismiss() navigationController?.popViewController(animated: true) + + SensorsAnalytics.Track.transaction( + chainType: token.chainId, + currencyType: token.symbol, + currencyNumber: Double(amountTextField.text ?? "0")!, + receiveAddress: addressTextField.text ?? "", + outcomeAddress: WalletRealmTool.getCurrentAppModel().currentWallet!.address, + transactionType: .normal + ) } } From d507a4f5c3cc0c43d24d0466b69c9a8fa4bb37bc Mon Sep 17 00:00:00 2001 From: LuFP Date: Mon, 12 Nov 2018 16:16:35 +0800 Subject: [PATCH 013/315] Remove CreatWalletViewModel and Sub2TableViewCell --- Neuron.xcodeproj/project.pbxproj | 16 +-- .../Sections/Wallet/Sub2TableViewCell.swift | 38 ------- Neuron/Sections/Wallet/Sub2TableViewCell.xib | 71 ------------ .../Sections/Wallet/View/AddWallet.storyboard | 34 +++--- .../ViewModel/CreatWalletViewModel.swift | 102 ------------------ 5 files changed, 18 insertions(+), 243 deletions(-) delete mode 100644 Neuron/Sections/Wallet/Sub2TableViewCell.swift delete mode 100644 Neuron/Sections/Wallet/Sub2TableViewCell.xib delete mode 100644 Neuron/Sections/Wallet/ViewModel/CreatWalletViewModel.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 599bc56d..d787b6c3 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -64,7 +64,6 @@ 6EF8F4102176029E004B7587 /* OpenAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF8F40F2176029E004B7587 /* OpenAuthViewController.swift */; }; 8905660720D0AC120041D4B4 /* AppModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8905660620D0AC120041D4B4 /* AppModel.swift */; }; 8913437120C78F1000A17AEF /* WalletModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8913437020C78F1000A17AEF /* WalletModel.swift */; }; - 8913437320C7B2F700A17AEF /* CreatWalletViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8913437220C7B2F700A17AEF /* CreatWalletViewModel.swift */; }; 8913437520C7D3A400A17AEF /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8913437420C7D3A400A17AEF /* Toast.swift */; }; 8913437820CA56CA00A17AEF /* RealmHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8913437720CA56CA00A17AEF /* RealmHelper.swift */; }; 891B663D213EAA1900B0FCB0 /* WalletManagement.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 891B663C213EAA1900B0FCB0 /* WalletManagement.storyboard */; }; @@ -77,8 +76,6 @@ 8928C25920AEB18100C3103E /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8928C25820AEB18100C3103E /* NibLoadable.swift */; }; 8928C26B20AED5C100C3103E /* SearchAppController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8928C26920AED5C100C3103E /* SearchAppController.swift */; }; 8928C27A20B2A61900C3103E /* WalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8928C27820B2A61900C3103E /* WalletViewController.swift */; }; - 8928C27E20B3AAFA00C3103E /* Sub2TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8928C27C20B3AAFA00C3103E /* Sub2TableViewCell.swift */; }; - 8928C27F20B3AAFA00C3103E /* Sub2TableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 8928C27D20B3AAFA00C3103E /* Sub2TableViewCell.xib */; }; 8928C2A320B41BCA00C3103E /* SelectWalletController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8928C2A120B41BCA00C3103E /* SelectWalletController.swift */; }; 892A1985215A55F400B2293D /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892A1984215A55F400B2293D /* Double+Extension.swift */; }; 893532F4217DECF400404C0B /* MessageSignShowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893532F3217DECF400404C0B /* MessageSignShowViewController.swift */; }; @@ -266,7 +263,6 @@ 71EE3A6382628C3632FE594F /* Pods-NeuronTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NeuronTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-NeuronTests/Pods-NeuronTests.release.xcconfig"; sourceTree = ""; }; 8905660620D0AC120041D4B4 /* AppModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppModel.swift; sourceTree = ""; }; 8913437020C78F1000A17AEF /* WalletModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletModel.swift; sourceTree = ""; }; - 8913437220C7B2F700A17AEF /* CreatWalletViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatWalletViewModel.swift; sourceTree = ""; }; 8913437420C7D3A400A17AEF /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; 8913437720CA56CA00A17AEF /* RealmHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmHelper.swift; sourceTree = ""; }; 891B663C213EAA1900B0FCB0 /* WalletManagement.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = WalletManagement.storyboard; sourceTree = ""; }; @@ -280,8 +276,6 @@ 8928C25820AEB18100C3103E /* NibLoadable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NibLoadable.swift; sourceTree = ""; }; 8928C26920AED5C100C3103E /* SearchAppController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchAppController.swift; sourceTree = ""; }; 8928C27820B2A61900C3103E /* WalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletViewController.swift; sourceTree = ""; }; - 8928C27C20B3AAFA00C3103E /* Sub2TableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sub2TableViewCell.swift; sourceTree = ""; }; - 8928C27D20B3AAFA00C3103E /* Sub2TableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Sub2TableViewCell.xib; sourceTree = ""; }; 8928C2A120B41BCA00C3103E /* SelectWalletController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletController.swift; sourceTree = ""; }; 892A1984215A55F400B2293D /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; 893532F3217DECF400404C0B /* MessageSignShowViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSignShowViewController.swift; sourceTree = ""; }; @@ -759,8 +753,6 @@ 8928C28020B3E60900C3103E /* Sub2CustomView */, 8928C27420B29AD800C3103E /* View */, 8928C27520B29AD800C3103E /* ViewModel */, - 8928C27C20B3AAFA00C3103E /* Sub2TableViewCell.swift */, - 8928C27D20B3AAFA00C3103E /* Sub2TableViewCell.xib */, ); path = Wallet; sourceTree = ""; @@ -795,7 +787,6 @@ 8928C27520B29AD800C3103E /* ViewModel */ = { isa = PBXGroup; children = ( - 8913437220C7B2F700A17AEF /* CreatWalletViewModel.swift */, 89D4AC3920DA31F60097E02B /* ImportWalletViewModel.swift */, 898CA98B20EA2F000059ECA3 /* AssetViewModel.swift */, ); @@ -1096,7 +1087,6 @@ 89D84CE3214790B5006B0287 /* NFTDetail.storyboard in Resources */, 898A1A2B20B7F0FC00ECB465 /* TradeDetailsController.xib in Resources */, 89F9E73F20DEBDE8009E68D4 /* ExportKeystoreController.xib in Resources */, - 8928C27F20B3AAFA00C3103E /* Sub2TableViewCell.xib in Resources */, 891B663D213EAA1900B0FCB0 /* WalletManagement.storyboard in Resources */, 896D043720AE69C5002CFF6A /* LaunchScreen.storyboard in Resources */, 898A1A1020B6539800ECB465 /* AddAssetTableViewCell.xib in Resources */, @@ -1299,7 +1289,6 @@ 898A19F020B5176A00ECB465 /* WalletDetailController.swift in Sources */, 8928C24320AE908300C3103E /* MainViewController.swift in Sources */, 8964A19620C12FB70086848F /* ButtonTagView.swift in Sources */, - 8928C27E20B3AAFA00C3103E /* Sub2TableViewCell.swift in Sources */, 893532F4217DECF400404C0B /* MessageSignShowViewController.swift in Sources */, 898A19F520B53F3800ECB465 /* DetailIconCell.swift in Sources */, 6E83CB90216F573100029324 /* GuideCollectionViewCell.swift in Sources */, @@ -1350,7 +1339,6 @@ 8913437120C78F1000A17AEF /* WalletModel.swift in Sources */, 6E9D7CBE216B386F0044176D /* AuthenticationViewController.swift in Sources */, 6E8168F7218874B1007641BA /* TransactionViewController.swift in Sources */, - 8913437320C7B2F700A17AEF /* CreatWalletViewModel.swift in Sources */, 6E9D7CC5216B490D0044176D /* AuthDeviceViewController.swift in Sources */, 89F7966D213A6B680064808A /* ERC721TableViewCell.swift in Sources */, 8964A19320C115A70086848F /* VerifyMnemonicViewController.swift in Sources */, @@ -1617,7 +1605,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = PBL67DWM43; ENABLE_BITCODE = YES; INFOPLIST_FILE = Neuron/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -1650,7 +1638,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = PBL67DWM43; ENABLE_BITCODE = YES; INFOPLIST_FILE = Neuron/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; diff --git a/Neuron/Sections/Wallet/Sub2TableViewCell.swift b/Neuron/Sections/Wallet/Sub2TableViewCell.swift deleted file mode 100644 index b8796add..00000000 --- a/Neuron/Sections/Wallet/Sub2TableViewCell.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// Sub2TableViewCell.swift -// Neuron -// -// Created by XiaoLu on 2018/5/22. -// Copyright © 2018年 cryptape. All rights reserved. -// - -import UIKit - -class Sub2TableViewCell: UITableViewCell { - - @IBOutlet weak var iconImage: UIImageView! - - @IBOutlet weak var titlelable: UILabel! - - @IBOutlet weak var countLabel: UILabel! - - var iconUrlStr: String? { - didSet { - iconImage.sd_setImage(with: URL(string: iconUrlStr!), placeholderImage: UIImage.init(named: "ETH_test"), options: .retryFailed, completed: nil) - } - } - - override func awakeFromNib() { - super.awakeFromNib() - - let lineV = UIView.init(frame: CGRect(x: 50, y: 59, width: ScreenSize.width - 60, height: 1)) - lineV.backgroundColor = ColorFromString(hex: "#eeeeee") - contentView.addSubview(lineV) - contentView.bringSubviewToFront(lineV) - } - - override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) - } - -} diff --git a/Neuron/Sections/Wallet/Sub2TableViewCell.xib b/Neuron/Sections/Wallet/Sub2TableViewCell.xib deleted file mode 100644 index cbd8a1a4..00000000 --- a/Neuron/Sections/Wallet/Sub2TableViewCell.xib +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Neuron/Sections/Wallet/View/AddWallet.storyboard b/Neuron/Sections/Wallet/View/AddWallet.storyboard index 0951f67b..4be96db5 100644 --- a/Neuron/Sections/Wallet/View/AddWallet.storyboard +++ b/Neuron/Sections/Wallet/View/AddWallet.storyboard @@ -808,12 +808,12 @@ - - + + @@ -1125,16 +1125,13 @@ - - + + diff --git a/Neuron/Sections/Wallet/ViewModel/CreatWalletViewModel.swift b/Neuron/Sections/Wallet/ViewModel/CreatWalletViewModel.swift deleted file mode 100644 index 8692ddbc..00000000 --- a/Neuron/Sections/Wallet/ViewModel/CreatWalletViewModel.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// CreatWalletViewModel.swift -// Neuron -// -// Created by XiaoLu on 2018/6/6. -// Copyright © 2018年 cryptape. All rights reserved. -// - -import UIKit - -@objc protocol CreatWalletViewModelDelegate { - func reloadView() - func doPush(mnemonic: String) -} - -protocol CreatWalletViewModelInterface { - var delegate: CreatWalletViewModelDelegate? {get set} - - var nameText: String {get} - var newPasswordText: String {get} - var againPasswordText: String {get} - var isFulfil: Bool {get} - - func textfieldTextChanged(text: String, indexPath: NSIndexPath) - func setNextButtonTitleColor() -> UIColor - func setNextButtonBackgroundColor() -> UIColor - - func goNextView() -} - -class CreatWalletViewModel: NSObject, CreatWalletViewModelInterface { - - weak var delegate: CreatWalletViewModelDelegate? - var nameText: String = "" - var newPasswordText: String = "" - var againPasswordText: String = "" - var isFulfil: Bool = false - var walletModel = WalletModel() - - func textfieldTextChanged(text: String, indexPath: NSIndexPath) { - switch indexPath.row { - case 0: - nameText = text - case 1: - newPasswordText = text - case 2: - againPasswordText = text - default: - break - } - if didJudgeButtonEnabled() { - isFulfil = true - } else { - isFulfil = false - } - delegate?.reloadView() - } - - func didJudgeButtonEnabled() -> Bool { - if nameText.count != 0 && newPasswordText.count != 0 && againPasswordText.count != 0 { - return true - } else { - return false - } - } - - func setNextButtonTitleColor() -> UIColor { - if didJudgeButtonEnabled() { - return ColorFromString(hex: "#ffffff") - } else { - return ColorFromString(hex: "#999999") - } - } - - func setNextButtonBackgroundColor() -> UIColor { - if didJudgeButtonEnabled() { - return ColorFromString(hex: "#2e4af2") - } else { - return ColorFromString(hex: "#e6e6e6") - } - } - - //在这处理数据的存储 - func goNextView() { - if case .invalid(let reason) = WalletNameValidator.validate(walletName: nameText) { - Toast.showToast(text: reason) - return - } - - if case .invalid(let reason) = PasswordValidator.validate(password: newPasswordText) { - Toast.showToast(text: reason) - return - } - - if newPasswordText != againPasswordText { - Toast.showToast(text: "两次密码不一致") - return - } - - delegate?.doPush(mnemonic: WalletManager.generateMnemonic()) - } -} From c40d0ce1a4cfdab11f46b6c6ed2e2a208031d671 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 12 Nov 2018 16:20:41 +0800 Subject: [PATCH 014/315] DApp call native function --- Neuron.xcodeproj/project.pbxproj | 36 ++++++++ .../Common/QRCodeViewController.swift | 4 +- .../Sections/Dapp/BrowserViewController.swift | 1 + .../DAppDeviceMotionMessageHandler.swift | 76 ++++++++++++++++ .../Native/DAppGyroscopeMessageHandler.swift | 76 ++++++++++++++++ .../Native/DAppNativeMessageHandler.swift | 88 +++++++++++++++++++ .../Native/DAppQRCodeMessageHandler.swift | 32 +++++++ .../Native/DAppTakePhotoMessageHandler.swift | 80 +++++++++++++++++ 8 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 Neuron/Sections/Dapp/Native/DAppDeviceMotionMessageHandler.swift create mode 100644 Neuron/Sections/Dapp/Native/DAppGyroscopeMessageHandler.swift create mode 100644 Neuron/Sections/Dapp/Native/DAppNativeMessageHandler.swift create mode 100644 Neuron/Sections/Dapp/Native/DAppQRCodeMessageHandler.swift create mode 100644 Neuron/Sections/Dapp/Native/DAppTakePhotoMessageHandler.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 0d4f9b2a..a0a39aef 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -60,6 +60,11 @@ 6E9D7CBE216B386F0044176D /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9D7CBC216B386F0044176D /* AuthenticationViewController.swift */; }; 6E9D7CC1216B3AE40044176D /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E9D7CC0216B3AE40044176D /* Authentication.storyboard */; }; 6E9D7CC5216B490D0044176D /* AuthDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9D7CC4216B490D0044176D /* AuthDeviceViewController.swift */; }; + 6EABFB0B21991BC200305ED5 /* DAppQRCodeMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EABFB0A21991BC200305ED5 /* DAppQRCodeMessageHandler.swift */; }; + 6EABFB0D21991CB300305ED5 /* DAppNativeMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EABFB0C21991CB300305ED5 /* DAppNativeMessageHandler.swift */; }; + 6EABFB10219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EABFB0F219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift */; }; + 6EABFB122199520F00305ED5 /* DAppTakePhotoMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EABFB112199520F00305ED5 /* DAppTakePhotoMessageHandler.swift */; }; + 6EABFB1421995AB800305ED5 /* DAppGyroscopeMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EABFB1321995AB800305ED5 /* DAppGyroscopeMessageHandler.swift */; }; 6EF8F40C2175CAD9004B7587 /* UIControl+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF8F40B2175CAD9004B7587 /* UIControl+Extension.swift */; }; 6EF8F4102176029E004B7587 /* OpenAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF8F40F2176029E004B7587 /* OpenAuthViewController.swift */; }; 8905660720D0AC120041D4B4 /* AppModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8905660620D0AC120041D4B4 /* AppModel.swift */; }; @@ -262,6 +267,11 @@ 6E9D7CBC216B386F0044176D /* AuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = ""; }; 6E9D7CC0216B3AE40044176D /* Authentication.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Authentication.storyboard; sourceTree = ""; }; 6E9D7CC4216B490D0044176D /* AuthDeviceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthDeviceViewController.swift; sourceTree = ""; }; + 6EABFB0A21991BC200305ED5 /* DAppQRCodeMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppQRCodeMessageHandler.swift; sourceTree = ""; }; + 6EABFB0C21991CB300305ED5 /* DAppNativeMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppNativeMessageHandler.swift; sourceTree = ""; }; + 6EABFB0F219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppDeviceMotionMessageHandler.swift; sourceTree = ""; }; + 6EABFB112199520F00305ED5 /* DAppTakePhotoMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppTakePhotoMessageHandler.swift; sourceTree = ""; }; + 6EABFB1321995AB800305ED5 /* DAppGyroscopeMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppGyroscopeMessageHandler.swift; sourceTree = ""; }; 6EF8F40B2175CAD9004B7587 /* UIControl+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIControl+Extension.swift"; sourceTree = ""; }; 6EF8F40F2176029E004B7587 /* OpenAuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAuthViewController.swift; sourceTree = ""; }; 71EE3A6382628C3632FE594F /* Pods-NeuronTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NeuronTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-NeuronTests/Pods-NeuronTests.release.xcconfig"; sourceTree = ""; }; @@ -592,6 +602,26 @@ path = Authentication; sourceTree = ""; }; + 6EABFB0921991AF900305ED5 /* Native */ = { + isa = PBXGroup; + children = ( + 6EABFB0E219930EC00305ED5 /* ViewController */, + 6EABFB0C21991CB300305ED5 /* DAppNativeMessageHandler.swift */, + 6EABFB112199520F00305ED5 /* DAppTakePhotoMessageHandler.swift */, + 6EABFB0A21991BC200305ED5 /* DAppQRCodeMessageHandler.swift */, + 6EABFB0F219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift */, + 6EABFB1321995AB800305ED5 /* DAppGyroscopeMessageHandler.swift */, + ); + path = Native; + sourceTree = ""; + }; + 6EABFB0E219930EC00305ED5 /* ViewController */ = { + isa = PBXGroup; + children = ( + ); + path = ViewController; + sourceTree = ""; + }; 8913436F20C78D9600A17AEF /* Models */ = { isa = PBXGroup; children = ( @@ -681,6 +711,7 @@ 8928C22720AE7CB500C3103E /* Dapp */ = { isa = PBXGroup; children = ( + 6EABFB0921991AF900305ED5 /* Native */, 8935BA5C216F48A800C37263 /* Method.swift */, 8935BA5E216F631A00C37263 /* ScriptMessageProxy.swift */, 89B895AA20F8507200B9468B /* BrowserUrlParser.swift */, @@ -1302,6 +1333,7 @@ 1A69465C21916BE5000093A2 /* EthereumTxSender.swift in Sources */, 898889CA2136528B00D04AA8 /* PrivatekeyViewController.swift in Sources */, 8935BA55216EE65500C37263 /* WKWebViewConfiguration.swift in Sources */, + 6EABFB122199520F00305ED5 /* DAppTakePhotoMessageHandler.swift in Sources */, 898A19F020B5176A00ECB465 /* WalletDetailController.swift in Sources */, 8928C24320AE908300C3103E /* MainViewController.swift in Sources */, 8964A19620C12FB70086848F /* ButtonTagView.swift in Sources */, @@ -1322,10 +1354,12 @@ 89FE21D820EC7E1800A09302 /* ERC20TokenService.swift in Sources */, 1A5515E521898A1000D34791 /* WalletKeystoreManager.swift in Sources */, 1A09224321357F3D00CAED5D /* TokensViewController.swift in Sources */, + 6EABFB10219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift in Sources */, 6EF8F40C2175CAD9004B7587 /* UIControl+Extension.swift in Sources */, 898CA98320EA0F210059ECA3 /* TokenModel.swift in Sources */, 8989E2D32188440D008A1BDE /* SwitchNetworkTableViewCell.swift in Sources */, 898A1A1720B6BE6E00ECB465 /* ChangePasswordController.swift in Sources */, + 6EABFB0D21991CB300305ED5 /* DAppNativeMessageHandler.swift in Sources */, 6E83CB91216F573100029324 /* GuideViewController.swift in Sources */, 8928C24520AE90C000C3103E /* BaseNavigationController.swift in Sources */, 6E83CB8E216F573100029324 /* GuideService.swift in Sources */, @@ -1414,6 +1448,7 @@ 8928C26B20AED5C100C3103E /* SearchAppController.swift in Sources */, 8981212A218D3161000813C8 /* QRCodeViewController.swift in Sources */, 8964A18D20C0E4BE0086848F /* GenerateMnemonicController.swift in Sources */, + 6EABFB0B21991BC200305ED5 /* DAppQRCodeMessageHandler.swift in Sources */, 6E4F961B2189A82900372D3E /* TransactionConfirmViewController.swift in Sources */, 89B296EB20F4CB6B008558A7 /* TransactionModel.swift in Sources */, 1A6946602192A0B0000093A2 /* GasCalculator.swift in Sources */, @@ -1422,6 +1457,7 @@ 89FE21DE20EDB70000A09302 /* ServiceErrors.swift in Sources */, 891C2483211165AC007C639D /* TokenMacro.swift in Sources */, 6E9D7CBA216B04EF0044176D /* AuthenticationService.swift in Sources */, + 6EABFB1421995AB800305ED5 /* DAppGyroscopeMessageHandler.swift in Sources */, 898889C62136521100D04AA8 /* KeystoreViewController.swift in Sources */, 8913437520C7D3A400A17AEF /* Toast.swift in Sources */, 89FC542D20D4142D00D5D27C /* SkipBackupFiles.swift in Sources */, diff --git a/Neuron/Sections/Common/QRCodeViewController.swift b/Neuron/Sections/Common/QRCodeViewController.swift index 8f658421..26f04f4c 100644 --- a/Neuron/Sections/Common/QRCodeViewController.swift +++ b/Neuron/Sections/Common/QRCodeViewController.swift @@ -10,8 +10,9 @@ import UIKit import AVFoundation import QRCodeReader -protocol QRCodeViewControllerDelegate: class { +@objc protocol QRCodeViewControllerDelegate: class { func didBackQRCodeMessage(codeResult: String) + @objc optional func qrcodeReaderDidCancel() } class QRCodeViewController: UIViewController { @@ -54,5 +55,6 @@ extension QRCodeViewController: QRCodeReaderViewControllerDelegate { func readerDidCancel(_ reader: QRCodeReaderViewController) { navigationController?.popViewController(animated: true) + delegate?.qrcodeReaderDidCancel?() } } diff --git a/Neuron/Sections/Dapp/BrowserViewController.swift b/Neuron/Sections/Dapp/BrowserViewController.swift index 9ac91302..aff47928 100644 --- a/Neuron/Sections/Dapp/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/BrowserViewController.swift @@ -27,6 +27,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable { webView.customUserAgent = "Neuron(Platform=iOS&AppVersion=\(String(describing: majorVersion!))" webView.navigationDelegate = self webView.uiDelegate = self + webView.addAllNativeFunctionHandler() return webView }() diff --git a/Neuron/Sections/Dapp/Native/DAppDeviceMotionMessageHandler.swift b/Neuron/Sections/Dapp/Native/DAppDeviceMotionMessageHandler.swift new file mode 100644 index 00000000..994b494d --- /dev/null +++ b/Neuron/Sections/Dapp/Native/DAppDeviceMotionMessageHandler.swift @@ -0,0 +1,76 @@ +// +// DAppDeviceMotionMessageHandler.swift +// Neuron +// +// Created by 晨风 on 2018/11/12. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import WebKit +import CoreMotion + +class DAppDeviceMotionMessageHandler: DAppNativeMessageHandler { + struct Parameters: Decodable { + let interval: Interval + } + enum Interval: String, Decodable { + case game + case ui + case normal + } + enum MessageName: String { + case startDeviceMotionListening + case stopDeviceMotionListening + } + override var messageNames: [String] { + return [ + MessageName.startDeviceMotionListening.rawValue, + MessageName.stopDeviceMotionListening.rawValue + ] + } + var motionManager: CMMotionManager? + + override func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + super.userContentController(userContentController, didReceive: message) + guard let data = try? JSONSerialization.data(withJSONObject: message.body, options: .prettyPrinted) else { return } + if message.name == MessageName.startDeviceMotionListening.rawValue { + guard motionManager == nil else { + self.callback(result: .success([:])) + return + } + let interval: Interval = (try? JSONDecoder().decode(Parameters.self, from: data))?.interval ?? .normal + let manager = CMMotionManager() + switch interval { + case .game: + manager.gyroUpdateInterval = 0.020 + case .ui: + manager.gyroUpdateInterval = 0.060 + case .normal: + manager.gyroUpdateInterval = 0.200 + } + manager.startDeviceMotionUpdates(to: OperationQueue.main, withHandler: { [weak self](motion, error) in + guard let motion = motion else { + self?.callback(result: .fail(-1, error?.localizedDescription ?? "监听设备方向失败")) + return + } + self?.motionDidUpdate(motion: motion) + }) + motionManager = manager + callback(result: .success([:])) + } else if message.name == MessageName.stopDeviceMotionListening.rawValue { + motionManager?.stopDeviceMotionUpdates() + motionManager = nil + callback(result: .success([:])) + } + } + + func motionDidUpdate(motion: CMDeviceMotion) { + let result: [String: Any] = [ + "alpha": motion.attitude.roll, + "beta": motion.attitude.pitch, + "gamma": motion.attitude.yaw + ] + callback(funcName: "onDeviceMotionChange", result: ["res": result]) + } +} diff --git a/Neuron/Sections/Dapp/Native/DAppGyroscopeMessageHandler.swift b/Neuron/Sections/Dapp/Native/DAppGyroscopeMessageHandler.swift new file mode 100644 index 00000000..8a10e6de --- /dev/null +++ b/Neuron/Sections/Dapp/Native/DAppGyroscopeMessageHandler.swift @@ -0,0 +1,76 @@ +// +// DAppGyroscopeMessageHandler.swift +// Neuron +// +// Created by 晨风 on 2018/11/12. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import WebKit +import CoreMotion + +class DAppGyroscopeMessageHandler: DAppNativeMessageHandler { + struct Parameters: Decodable { + let interval: Interval + } + enum Interval: String, Decodable { + case game + case ui + case normal + } + enum MessageName: String { + case startGyroscope + case stopGyroscope + } + override var messageNames: [String] { + return [ + MessageName.startGyroscope.rawValue, + MessageName.stopGyroscope.rawValue + ] + } + var motionManager: CMMotionManager? + + override func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + super.userContentController(userContentController, didReceive: message) + guard let data = try? JSONSerialization.data(withJSONObject: message.body, options: .prettyPrinted) else { return } + if message.name == MessageName.startGyroscope.rawValue { + guard motionManager == nil else { + self.callback(result: .success([:])) + return + } + let interval: Interval = (try? JSONDecoder().decode(Parameters.self, from: data))?.interval ?? .normal + let manager = CMMotionManager() + switch interval { + case .game: + manager.gyroUpdateInterval = 0.020 + case .ui: + manager.gyroUpdateInterval = 0.060 + case .normal: + manager.gyroUpdateInterval = 0.200 + } + manager.startDeviceMotionUpdates(to: OperationQueue.main, withHandler: { [weak self](motion, error) in + guard let motion = motion else { + self?.callback(result: .fail(-1, error?.localizedDescription ?? "监听陀螺仪数据失败")) + return + } + self?.motionDidUpdate(motion: motion) + }) + motionManager = manager + callback(result: .success([:])) + } else if message.name == MessageName.stopGyroscope.rawValue { + motionManager?.stopDeviceMotionUpdates() + motionManager = nil + callback(result: .success([:])) + } + } + + func motionDidUpdate(motion: CMDeviceMotion) { + let result: [String: Any] = [ + "x": motion.rotationRate.x, + "y": motion.rotationRate.y, + "z": motion.rotationRate.z + ] + callback(funcName: "onGyroscopeChange", result: ["res": result]) + } +} diff --git a/Neuron/Sections/Dapp/Native/DAppNativeMessageHandler.swift b/Neuron/Sections/Dapp/Native/DAppNativeMessageHandler.swift new file mode 100644 index 00000000..62191b96 --- /dev/null +++ b/Neuron/Sections/Dapp/Native/DAppNativeMessageHandler.swift @@ -0,0 +1,88 @@ +// +// DAppNativeMessageHandler.swift +// Neuron +// +// Created by 晨风 on 2018/11/12. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import WebKit + +class DAppNativeMessageHandler: NSObject, WKScriptMessageHandler { + enum Result { + case success([String: Any]) + case fail(Int, String) + } + struct Callback: Decodable { + let callback: String + } + + var callback: String? + weak var webView: WKWebView? + var messageNames: [String] { return [] } + + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + webView = message.webView + guard let data = try? JSONSerialization.data(withJSONObject: message.body, options: .prettyPrinted) else { return } + callback = try? JSONDecoder().decode(Callback.self, from: data).callback + } + + func callback(result: Result) { + guard let callback = callback else { return } + let resultDict: [String: Any] + switch result { + case .success(let info): + resultDict = [ + "status": 1, + "info": info + ] + case .fail(let code, let msg): + resultDict = [ + "status": 0, + "errorCode": code, + "errorMsg": msg + ] + } + self.callback(funcName: callback, result: resultDict) + } + + func callback(funcName: String, result: [String: Any]) { + guard let data = try? JSONSerialization.data(withJSONObject: result, options: .prettyPrinted) else { return } + let string = String(bytes: data.bytes, encoding: .utf8) ?? "" + let js = "\(funcName)(\'\(string)\')" + webView?.evaluateJavaScript(js, completionHandler: nil) + } +} + +extension WKWebView { + func addNativeFunctionHandler(handler: DAppNativeMessageHandler) { + for name in handler.messageNames { + configuration.userContentController.add(handler, name: name) + } + } + + func addAllNativeFunctionHandler() { +// addNativeFunctionHandler(handler: DAppTakePhotoMessageHandler()) + addNativeFunctionHandler(handler: DAppQRCodeMessageHandler()) + addNativeFunctionHandler(handler: DAppDeviceMotionMessageHandler()) + addNativeFunctionHandler(handler: DAppGyroscopeMessageHandler()) + + // test +// let js = "window.webkit.messageHandlers.takePhoto.postMessage({quality: 'normal', callback: 'callback'})" +// let js = "window.webkit.messageHandlers.scanCode.postMessage({callback: 'callback'})" +// let js = "window.webkit.messageHandlers.startDeviceMotionListening.postMessage({interval: 'normal', callback: 'callback'})" +// let js = "window.webkit.messageHandlers.startGyroscope.postMessage({interval: 'normal', callback: 'callback'})" +// configuration.userContentController.addUserScript(WKUserScript(source: js, injectionTime: .atDocumentEnd, forMainFrameOnly: false)) + } +} + +extension UIResponder { + var viewController: UIViewController? { + if self.isKind(of: UIViewController.self) { + return self as? UIViewController + } else { + return next?.viewController + } + } +} diff --git a/Neuron/Sections/Dapp/Native/DAppQRCodeMessageHandler.swift b/Neuron/Sections/Dapp/Native/DAppQRCodeMessageHandler.swift new file mode 100644 index 00000000..22f74eda --- /dev/null +++ b/Neuron/Sections/Dapp/Native/DAppQRCodeMessageHandler.swift @@ -0,0 +1,32 @@ +// +// DAppQRCodeMessageHandler.swift +// Neuron +// +// Created by 晨风 on 2018/11/12. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import WebKit + +class DAppQRCodeMessageHandler: DAppNativeMessageHandler, QRCodeViewControllerDelegate { + override var messageNames: [String] { + return ["scanCode"] + } + + override func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + super.userContentController(userContentController, didReceive: message) + let controller = QRCodeViewController() + controller.delegate = self + message.webView?.viewController?.navigationController?.pushViewController(controller, animated: true) + } + + // MARK: - QRCodeViewControllerDelegate + func didBackQRCodeMessage(codeResult: String) { + callback(result: .success(["result": codeResult])) + } + + func qrcodeReaderDidCancel() { + callback(result: .fail(-1, "用户取消")) + } +} diff --git a/Neuron/Sections/Dapp/Native/DAppTakePhotoMessageHandler.swift b/Neuron/Sections/Dapp/Native/DAppTakePhotoMessageHandler.swift new file mode 100644 index 00000000..475d141e --- /dev/null +++ b/Neuron/Sections/Dapp/Native/DAppTakePhotoMessageHandler.swift @@ -0,0 +1,80 @@ +// +// DAppTakePhotoMessageHandler.swift +// Neuron +// +// Created by 晨风 on 2018/11/12. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import WebKit +import AVFoundation + +class DAppTakePhotoMessageHandler: DAppNativeMessageHandler, UIImagePickerControllerDelegate, UINavigationControllerDelegate { + override var messageNames: [String] { + return ["takePhoto"] + } + enum Quality: String, Decodable { + case high + case normal + case low + } + struct Parameters: Decodable { + var quality: Quality? + } + var quality: Quality? + + override func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + super.userContentController(userContentController, didReceive: message) + guard let data = try? JSONSerialization.data(withJSONObject: message.body, options: .prettyPrinted) else { return } + quality = try? JSONDecoder().decode(Parameters.self, from: data).quality ?? .normal + + AVCaptureDevice.requestAccess(for: AVMediaType.video) { [weak self](result) in + guard let self = self else { return } + if result { + let controller = UIImagePickerController() + controller.delegate = self + controller.sourceType = .photoLibrary + controller.allowsEditing = false + DispatchQueue.main.async { + self.webView?.viewController?.present(controller, animated: true, completion: nil) + } + } else { + let alert = UIAlertController(title: "", message: "拍照需要相机访问权限", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "开启", style: .default, handler: { (_) in + let url = URL(string: UIApplication.openSettingsURLString)! + UIApplication.shared.open(url, options: [:], completionHandler: nil) + })) + alert.addAction(UIAlertAction(title: "取消", style: .default, handler: { (_) in + alert.dismiss(animated: true, completion: nil) + self.callback(result: .fail(-1, "无相机访问权限")) + })) + DispatchQueue.main.async { + self.webView?.viewController?.present(alert, animated: true, completion: nil) + } + } + } + } + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { + picker.dismiss(animated: true, completion: nil) + guard let image = info[.originalImage] as? UIImage else { return } + let resultImageData: Data + if quality == .high { + resultImageData = image.jpegData(compressionQuality: 1.0)! + } else if quality == .low { + resultImageData = image.jpegData(compressionQuality: 0.5)! + } else { + resultImageData = image.jpegData(compressionQuality: 0.75)! + } + let cachesDirectory = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!, isDirectory: true) + let cachePath = cachesDirectory.appendingPathComponent("\(Date().timeIntervalSince1970).jpeg") + try? resultImageData.write(to: cachePath) + callback(result: .success(["imagePath": cachePath.absoluteString])) + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + picker.dismiss(animated: true, completion: nil) + callback(result: .fail(-1, "用户取消")) + } +} From 2d01a3554bc4947bb655914b49b83c04f75359f4 Mon Sep 17 00:00:00 2001 From: LuFP Date: Mon, 12 Nov 2018 17:11:49 +0800 Subject: [PATCH 015/315] Remove ImportWalletViewModel --- Neuron.xcodeproj/project.pbxproj | 8 +- .../Wallet/View/KeystoreViewController.swift | 75 ++++++- .../Wallet/View/MnemonicViewController.swift | 80 ++++++- .../View/PrivatekeyViewController.swift | 78 ++++++- .../ViewModel/ImportWalletViewModel.swift | 201 ------------------ 5 files changed, 217 insertions(+), 225 deletions(-) delete mode 100644 Neuron/Sections/Wallet/ViewModel/ImportWalletViewModel.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index d787b6c3..6be835d7 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -163,7 +163,6 @@ 89D4AC2E20D59F270097E02B /* WalletManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D4AC2520D59F270097E02B /* WalletManager.swift */; }; 89D4AC3620D8AFC50097E02B /* WalletRealmTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D4AC3520D8AFC50097E02B /* WalletRealmTool.swift */; }; 89D4AC3820D8DB610097E02B /* NotificationName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D4AC3720D8DB610097E02B /* NotificationName.swift */; }; - 89D4AC3A20DA31F60097E02B /* ImportWalletViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D4AC3920DA31F60097E02B /* ImportWalletViewModel.swift */; }; 89D84CE121475755006B0287 /* NFTService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D84CE021475755006B0287 /* NFTService.swift */; }; 89D84CE3214790B5006B0287 /* NFTDetail.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 89D84CE2214790B5006B0287 /* NFTDetail.storyboard */; }; 89D84CE72147C671006B0287 /* TraitsCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D84CE62147C671006B0287 /* TraitsCollectionViewCell.swift */; }; @@ -370,7 +369,6 @@ 89D4AC2520D59F270097E02B /* WalletManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletManager.swift; sourceTree = ""; }; 89D4AC3520D8AFC50097E02B /* WalletRealmTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletRealmTool.swift; sourceTree = ""; }; 89D4AC3720D8DB610097E02B /* NotificationName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationName.swift; sourceTree = ""; }; - 89D4AC3920DA31F60097E02B /* ImportWalletViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportWalletViewModel.swift; sourceTree = ""; }; 89D84CE021475755006B0287 /* NFTService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFTService.swift; sourceTree = ""; }; 89D84CE2214790B5006B0287 /* NFTDetail.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = NFTDetail.storyboard; sourceTree = ""; }; 89D84CE62147C671006B0287 /* TraitsCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TraitsCollectionViewCell.swift; sourceTree = ""; }; @@ -787,7 +785,6 @@ 8928C27520B29AD800C3103E /* ViewModel */ = { isa = PBXGroup; children = ( - 89D4AC3920DA31F60097E02B /* ImportWalletViewModel.swift */, 898CA98B20EA2F000059ECA3 /* AssetViewModel.swift */, ); path = ViewModel; @@ -1363,7 +1360,6 @@ 89C3BF09213521DE00872BD0 /* UIView+Extension.swift in Sources */, 896D042D20AE69C1002CFF6A /* AppDelegate.swift in Sources */, 6E867E03216C3EC800BD6FE5 /* AuthSelectWalletViewController.swift in Sources */, - 89D4AC3A20DA31F60097E02B /* ImportWalletViewModel.swift in Sources */, 89BEBE6B20EF82B6007D2705 /* EthereumNetwork.swift in Sources */, 89BEBE7220F12647007D2705 /* CommonWebViewController.swift in Sources */, 6E867E05216C3F1500BD6FE5 /* AuthPasswordViewController.swift in Sources */, @@ -1605,7 +1601,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = PBL67DWM43; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = YES; INFOPLIST_FILE = Neuron/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -1638,7 +1634,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = PBL67DWM43; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = YES; INFOPLIST_FILE = Neuron/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; diff --git a/Neuron/Sections/Wallet/View/KeystoreViewController.swift b/Neuron/Sections/Wallet/View/KeystoreViewController.swift index fabcda90..4803a8b9 100644 --- a/Neuron/Sections/Wallet/View/KeystoreViewController.swift +++ b/Neuron/Sections/Wallet/View/KeystoreViewController.swift @@ -8,21 +8,21 @@ import UIKit import RSKPlaceholderTextView +import EthereumAddress +import IGIdenticon -class KeystoreViewController: UITableViewController, ImportWalletViewModelDelegate, QRCodeViewControllerDelegate { +class KeystoreViewController: UITableViewController, QRCodeViewControllerDelegate { @IBOutlet weak var importButton: UIButton! @IBOutlet weak var keyStoreTextView: RSKPlaceholderTextView! var name: String? = "" var password: String? = "" var keystore: String? = "" - let viewModel = ImportWalletViewModel() @IBOutlet weak var titleContentView: UIView! @IBOutlet weak var titleLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() keyStoreTextView.delegate = self - viewModel.delegate = self let titleHeight = titleLabel.textRect(forBounds: CGRect(x: 0, y: 0, width: titleLabel.bounds.size.width, height: 100), limitedToNumberOfLines: 0).size.height titleContentView.frame = CGRect(origin: titleContentView.frame.origin, size: CGSize(width: titleContentView.bounds.size.width, height: max(titleHeight + 12, 35.0))) @@ -63,11 +63,74 @@ class KeystoreViewController: UITableViewController, ImportWalletViewModelDelega } @IBAction func importWallet(_ sender: UIButton) { - viewModel.importKeystoreWallet(keystore: keystore!, password: password!, name: name!) + importKeystoreWallet(keystore: keystore!, password: password!, name: name!) } - func didPopToRootView() { - navigationController?.popToRootViewController(animated: true) + func importKeystoreWallet(keystore: String, password: String, name: String) { + if keystore.isEmpty { + Toast.showToast(text: "请输入keystore文本") + return + } + + if case .invalid(let reason) = WalletNameValidator.validate(walletName: name) { + Toast.showToast(text: reason) + return + } + if password.isEmpty { + Toast.showToast(text: "解锁密码不能为空") + return + } + let walletModel = WalletModel() + walletModel.name = name + Toast.showHUD(text: "导入钱包中") + DispatchQueue.global(qos: .userInitiated).async { + do { + let wallet = try WalletManager.default.importKeystore(keystore, password: password) + DispatchQueue.main.async { + Toast.hideHUD() + walletModel.address = EthereumAddress.toChecksumAddress(wallet.address)! + self.saveWalletToRealm(with: walletModel) + SensorsAnalytics.Track.importWallet(type: .keystore, address: walletModel.address) + } + } catch let error { + DispatchQueue.main.async { + Toast.hideHUD() + Toast.showToast(text: error.localizedDescription) + SensorsAnalytics.Track.importWallet(type: .keystore, address: nil) + } + } + } + } + + private func saveWalletToRealm(with walletModel: WalletModel) { + let appModel = WalletRealmTool.getCurrentAppModel() + let result: [WalletModel] = appModel.wallets.filter { (wallet) -> Bool in + return wallet.address == walletModel.address + } + if result.count >= 1 { + Toast.showToast(text: "已存在该钱包") + return + } + + let isFirstWallet = appModel.wallets.count == 0 + let iconImage = GitHubIdenticon().icon(from: walletModel.address.lowercased(), size: CGSize(width: 60, height: 60)) + walletModel.iconData = iconImage!.pngData()! + do { + try WalletRealmTool.realm.write { + appModel.currentWallet = walletModel + appModel.wallets.append(walletModel) + WalletRealmTool.addObject(appModel: appModel) + } + Toast.showToast(text: "导入成功") + SensorsAnalytics.Track.importWallet(type: .keystore, address: walletModel.address) + if isFirstWallet { + NotificationCenter.default.post(name: .firstWalletCreated, object: nil) + } + NotificationCenter.default.post(name: .createWalletSuccess, object: nil, userInfo: ["address": walletModel.address]) + navigationController?.popToRootViewController(animated: true) + } catch { + Toast.showToast(text: error.localizedDescription) + } } } diff --git a/Neuron/Sections/Wallet/View/MnemonicViewController.swift b/Neuron/Sections/Wallet/View/MnemonicViewController.swift index aba785a3..7af3fc81 100644 --- a/Neuron/Sections/Wallet/View/MnemonicViewController.swift +++ b/Neuron/Sections/Wallet/View/MnemonicViewController.swift @@ -8,8 +8,10 @@ import UIKit import RSKPlaceholderTextView +import EthereumAddress +import IGIdenticon -class MnemonicViewController: UITableViewController, ImportWalletViewModelDelegate, QRCodeViewControllerDelegate { +class MnemonicViewController: UITableViewController, QRCodeViewControllerDelegate { @IBOutlet weak var importButton: UIButton! @IBOutlet weak var mnemonicTextView: RSKPlaceholderTextView! @@ -18,12 +20,10 @@ class MnemonicViewController: UITableViewController, ImportWalletViewModelDelega var password: String? = "" var confirmPassword: String? = "" var mnemonic: String? = "" - let viewModel = ImportWalletViewModel() override func viewDidLoad() { super.viewDidLoad() mnemonicTextView.delegate = self - viewModel.delegate = self } @IBAction func nameChanged(_ sender: UITextField) { @@ -72,11 +72,79 @@ class MnemonicViewController: UITableViewController, ImportWalletViewModelDelega } @IBAction func importWallet(_ sender: UIButton) { - viewModel.importWalletWithMnemonic(mnemonic: mnemonic!, password: password!, confirmPassword: confirmPassword!, name: name!) + importWalletWithMnemonic(mnemonic: mnemonic!, password: password!, confirmPassword: confirmPassword!, name: name!) } - func didPopToRootView() { - navigationController?.popToRootViewController(animated: true) + func importWalletWithMnemonic(mnemonic: String, password: String, confirmPassword: String, devirationPath: String = "m/44'/60'/0'/0/0", name: String) { + if mnemonic.isEmpty { + Toast.showToast(text: "请输入助记词") + return + } + + if case .invalid(let reason) = WalletNameValidator.validate(walletName: name) { + Toast.showToast(text: reason) + return + } + + if case .invalid(let reason) = PasswordValidator.validate(password: password) { + Toast.showToast(text: reason) + return + } + if password != confirmPassword { + Toast.showToast(text: "两次密码输入不一致") + return + } + let walletModel = WalletModel() + walletModel.name = name + Toast.showHUD(text: "导入钱包中") + DispatchQueue.global(qos: .userInitiated).async { + do { + let wallet = try WalletManager.default.importMnemonic(mnemonic: mnemonic, password: password) + DispatchQueue.main.async { + Toast.hideHUD() + walletModel.address = EthereumAddress.toChecksumAddress(wallet.address)! + self.saveWalletToRealm(with: walletModel) + SensorsAnalytics.Track.importWallet(type: .mnemonic, address: walletModel.address) + } + } catch let error { + DispatchQueue.main.async { + Toast.hideHUD() + Toast.showToast(text: error.localizedDescription) + SensorsAnalytics.Track.importWallet(type: .mnemonic, address: nil) + } + } + } + } + + private func saveWalletToRealm(with walletModel: WalletModel) { + let appModel = WalletRealmTool.getCurrentAppModel() + let result: [WalletModel] = appModel.wallets.filter { (wallet) -> Bool in + return wallet.address == walletModel.address + } + if result.count >= 1 { + Toast.showToast(text: "已存在该钱包") + return + } + + let isFirstWallet = appModel.wallets.count == 0 + let iconImage = GitHubIdenticon().icon(from: walletModel.address.lowercased(), size: CGSize(width: 60, height: 60)) + walletModel.iconData = iconImage!.pngData()! + do { + try WalletRealmTool.realm.write { + appModel.currentWallet = walletModel + appModel.wallets.append(walletModel) + WalletRealmTool.addObject(appModel: appModel) + } + Toast.showToast(text: "导入成功") + SensorsAnalytics.Track.importWallet(type: .keystore, address: walletModel.address) + if isFirstWallet { + NotificationCenter.default.post(name: .firstWalletCreated, object: nil) + } + NotificationCenter.default.post(name: .createWalletSuccess, object: nil, userInfo: ["address": walletModel.address]) + navigationController?.popToRootViewController(animated: true) + } catch { + Toast.showToast(text: error.localizedDescription) + } } } diff --git a/Neuron/Sections/Wallet/View/PrivatekeyViewController.swift b/Neuron/Sections/Wallet/View/PrivatekeyViewController.swift index def55000..2cb9b5ea 100644 --- a/Neuron/Sections/Wallet/View/PrivatekeyViewController.swift +++ b/Neuron/Sections/Wallet/View/PrivatekeyViewController.swift @@ -8,20 +8,20 @@ import UIKit import RSKPlaceholderTextView +import EthereumAddress +import IGIdenticon -class PrivatekeyViewController: UITableViewController, ImportWalletViewModelDelegate, QRCodeViewControllerDelegate, EnterBackOverlayPresentable { +class PrivatekeyViewController: UITableViewController, QRCodeViewControllerDelegate, EnterBackOverlayPresentable { @IBOutlet weak var importButton: UIButton! @IBOutlet weak var privatekeyTextView: RSKPlaceholderTextView! var name: String? = "" var password: String? = "" var confirmPassword: String? = "" var privateKey: String? = "" - let viewModel = ImportWalletViewModel() override func viewDidLoad() { super.viewDidLoad() privatekeyTextView.delegate = self - viewModel.delegate = self setupEnterBackOverlay() } @@ -71,11 +71,77 @@ class PrivatekeyViewController: UITableViewController, ImportWalletViewModelDele } @IBAction func importWallet(_ sender: UIButton) { - viewModel.importPrivateWallet(privateKey: privateKey!, password: password!, confirmPassword: confirmPassword!, name: name!) + importPrivateWallet(privateKey: privateKey!, password: password!, confirmPassword: confirmPassword!, name: name!) } - func didPopToRootView() { - navigationController?.popToRootViewController(animated: true) + func importPrivateWallet(privateKey: String, password: String, confirmPassword: String, name: String) { + if privateKey.isEmpty { + Toast.showToast(text: "请输入私钥") + return + } + if case .invalid(let reason) = WalletNameValidator.validate(walletName: name) { + Toast.showToast(text: reason) + return + } + if case .invalid(let reason) = PasswordValidator.validate(password: password) { + Toast.showToast(text: reason) + return + } + if password != confirmPassword { + Toast.showToast(text: "两次密码输入不一致") + return + } + let walletModel = WalletModel() + walletModel.name = name + Toast.showHUD(text: "导入钱包中") + DispatchQueue.global(qos: .userInitiated).async { + do { + let wallet = try WalletManager.default.importPrivateKey(privateKey: privateKey, password: password) + DispatchQueue.main.async { + Toast.hideHUD() + walletModel.address = EthereumAddress.toChecksumAddress(wallet.address)! + self.saveWalletToRealm(with: walletModel) + SensorsAnalytics.Track.importWallet(type: .privateKey, address: walletModel.address) + } + } catch let error { + DispatchQueue.main.async { + Toast.hideHUD() + Toast.showToast(text: error.localizedDescription) + SensorsAnalytics.Track.importWallet(type: .privateKey, address: nil) + } + } + } + } + + private func saveWalletToRealm(with walletModel: WalletModel) { + let appModel = WalletRealmTool.getCurrentAppModel() + let result: [WalletModel] = appModel.wallets.filter { (wallet) -> Bool in + return wallet.address == walletModel.address + } + if result.count >= 1 { + Toast.showToast(text: "已存在该钱包") + return + } + + let isFirstWallet = appModel.wallets.count == 0 + let iconImage = GitHubIdenticon().icon(from: walletModel.address.lowercased(), size: CGSize(width: 60, height: 60)) + walletModel.iconData = iconImage!.pngData()! + do { + try WalletRealmTool.realm.write { + appModel.currentWallet = walletModel + appModel.wallets.append(walletModel) + WalletRealmTool.addObject(appModel: appModel) + } + Toast.showToast(text: "导入成功") + SensorsAnalytics.Track.importWallet(type: .keystore, address: walletModel.address) + if isFirstWallet { + NotificationCenter.default.post(name: .firstWalletCreated, object: nil) + } + NotificationCenter.default.post(name: .createWalletSuccess, object: nil, userInfo: ["address": walletModel.address]) + navigationController?.popToRootViewController(animated: true) + } catch { + Toast.showToast(text: error.localizedDescription) + } } } diff --git a/Neuron/Sections/Wallet/ViewModel/ImportWalletViewModel.swift b/Neuron/Sections/Wallet/ViewModel/ImportWalletViewModel.swift deleted file mode 100644 index 1d3a6bfb..00000000 --- a/Neuron/Sections/Wallet/ViewModel/ImportWalletViewModel.swift +++ /dev/null @@ -1,201 +0,0 @@ -// -// ImportWalletViewModel.swift -// Neuron -// -// Created by XiaoLu on 2018/6/20. -// Copyright © 2018年 cryptape. All rights reserved. -// - -import UIKit -import Web3swift -import EthereumAddress -import IGIdenticon - -protocol ImportWalletViewModelDelegate: class { - func didPopToRootView() -} - -enum ImportWalletType { - case keystoreType - case mnemonicType - case privateKeyType -} - -class ImportWalletViewModel: NSObject { - - weak var delegate: ImportWalletViewModelDelegate? - var walletModel = WalletModel() - var importType = ImportWalletType.keystoreType - - /// if change the way to import wallet the walletModel should be empy - func changeImportWay() { - walletModel = WalletModel() - } - - /// import keyStore wallet - /// if import wallet way is keystore or privatekey,there is no mnemonic - /// - Parameters: - /// - keystore: keystore - /// - password: password - /// - name: walletName - func importKeystoreWallet(keystore: String, password: String, name: String) { - if keystore.isEmpty { - Toast.showToast(text: "请输入keystore文本") - return - } - - if case .invalid(let reason) = WalletNameValidator.validate(walletName: name) { - Toast.showToast(text: reason) - return - } - if password.isEmpty { - Toast.showToast(text: "解锁密码不能为空") - return - } - - walletModel.name = name - Toast.showHUD(text: "导入钱包中") - DispatchQueue.global(qos: .userInitiated).async { - do { - let wallet = try WalletManager.default.importKeystore(keystore, password: password) - DispatchQueue.main.async { - Toast.hideHUD() - self.walletModel.address = EthereumAddress.toChecksumAddress(wallet.address)! - self.saveWalletToRealm() - SensorsAnalytics.Track.importWallet(type: .keystore, address: self.walletModel.address) - } - } catch let error { - DispatchQueue.main.async { - Toast.hideHUD() - Toast.showToast(text: error.localizedDescription) - SensorsAnalytics.Track.importWallet(type: .keystore, address: nil) - } - } - } - } - - private func saveWalletToRealm() { - let appModel = WalletRealmTool.getCurrentAppModel() - let result: [WalletModel] = appModel.wallets.filter { (wallet) -> Bool in - return wallet.address == self.walletModel.address - } - if result.count >= 1 { - Toast.showToast(text: "已存在该钱包") - return - } - - let isFirstWallet = appModel.wallets.count == 0 - let iconImage = GitHubIdenticon().icon(from: walletModel.address.lowercased(), size: CGSize(width: 60, height: 60)) - walletModel.iconData = iconImage!.pngData()! - do { - try WalletRealmTool.realm.write { - appModel.currentWallet = walletModel - appModel.wallets.append(walletModel) - WalletRealmTool.addObject(appModel: appModel) - } - Toast.showToast(text: "导入成功") - SensorsAnalytics.Track.importWallet(type: .keystore, address: self.walletModel.address) - if isFirstWallet { - NotificationCenter.default.post(name: .firstWalletCreated, object: nil) - } - NotificationCenter.default.post(name: .createWalletSuccess, object: nil, userInfo: ["address": walletModel.address]) - delegate?.didPopToRootView() - } catch { - Toast.showToast(text: error.localizedDescription) - } - } - - /// import wallet with mnemonic - /// - /// - Parameters: - /// - mnemonic: mnemonic - /// - password: password - /// - devirationPath: devirationPath - /// - name: walletname - func importWalletWithMnemonic(mnemonic: String, password: String, confirmPassword: String, devirationPath: String = "m/44'/60'/0'/0/0", name: String) { - if mnemonic.isEmpty { - Toast.showToast(text: "请输入助记词") - return - } - - if case .invalid(let reason) = WalletNameValidator.validate(walletName: name) { - Toast.showToast(text: reason) - return - } - - if case .invalid(let reason) = PasswordValidator.validate(password: password) { - Toast.showToast(text: reason) - return - } - if password != confirmPassword { - Toast.showToast(text: "两次密码输入不一致") - return - } - - walletModel.name = name - Toast.showHUD(text: "导入钱包中") - DispatchQueue.global(qos: .userInitiated).async { - do { - let wallet = try WalletManager.default.importMnemonic(mnemonic: mnemonic, password: password) - DispatchQueue.main.async { - Toast.hideHUD() - self.walletModel.address = EthereumAddress.toChecksumAddress(wallet.address)! - self.saveWalletToRealm() - SensorsAnalytics.Track.importWallet(type: .mnemonic, address: self.walletModel.address) - } - } catch let error { - DispatchQueue.main.async { - Toast.hideHUD() - Toast.showToast(text: error.localizedDescription) - SensorsAnalytics.Track.importWallet(type: .mnemonic, address: nil) - } - } - } - } - - /// import wallet with privatekey - /// - /// - Parameters: - /// - privateKey: privateKey - /// - password: password - /// - confirmPassword: confirmPassword - /// - name: name - func importPrivateWallet(privateKey: String, password: String, confirmPassword: String, name: String) { - if privateKey.isEmpty { - Toast.showToast(text: "请输入私钥") - return - } - if case .invalid(let reason) = WalletNameValidator.validate(walletName: name) { - Toast.showToast(text: reason) - return - } - if case .invalid(let reason) = PasswordValidator.validate(password: password) { - Toast.showToast(text: reason) - return - } - if password != confirmPassword { - Toast.showToast(text: "两次密码输入不一致") - return - } - - walletModel.name = name - Toast.showHUD(text: "导入钱包中") - DispatchQueue.global(qos: .userInitiated).async { - do { - let wallet = try WalletManager.default.importPrivateKey(privateKey: privateKey, password: password) - DispatchQueue.main.async { - Toast.hideHUD() - self.walletModel.address = EthereumAddress.toChecksumAddress(wallet.address)! - self.saveWalletToRealm() - SensorsAnalytics.Track.importWallet(type: .privateKey, address: self.walletModel.address) - } - } catch let error { - DispatchQueue.main.async { - Toast.hideHUD() - Toast.showToast(text: error.localizedDescription) - SensorsAnalytics.Track.importWallet(type: .privateKey, address: nil) - } - } - } - } -} From 0c3838db59032891c341c12ad1dfbd5b697c2a38 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 12 Nov 2018 17:19:04 +0800 Subject: [PATCH 016/315] Add `ip_id` in sensors --- Neuron/Services/Common/SensorsAnalytics.swift | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/Neuron/Services/Common/SensorsAnalytics.swift b/Neuron/Services/Common/SensorsAnalytics.swift index 76cf8407..9236d921 100644 --- a/Neuron/Services/Common/SensorsAnalytics.swift +++ b/Neuron/Services/Common/SensorsAnalytics.swift @@ -12,6 +12,7 @@ import SensorsAnalyticsSDK class SensorsAnalytics { enum UserDefaultsKey: String { case userId = "SensorsAnalyticsUserIdUserDefaultsKey" + case ipId = "SensorsAnalyticsIpIdUserDefaultsKey" } fileprivate let sensors: SensorsAnalyticsSDK static let service = SensorsAnalytics() @@ -22,12 +23,8 @@ class SensorsAnalytics { #else sensors = SensorsAnalyticsSDK.sharedInstance(withServerURL: "https://banana.cryptape.com:8106/sa?project=production", andDebugMode: .off) #endif - sensors.enableLog(false) - sensors.enableTrackGPSLocation(false) - sensors.enableTrackScreenOrientation(false) - sensors.login(getUserId()) - sensors.trackAppCrash() - sensors.registerSuperProperties(["platformType": "iOS"]) + + sensors.registerSuperProperties(["platformType": "iOS", "ip_id": getIpId()]) let eventType: SensorsAnalyticsAutoTrackEventType = [ .eventTypeAppStart, .eventTypeAppEnd, @@ -35,6 +32,11 @@ class SensorsAnalytics { .eventTypeAppClick ] sensors.enableAutoTrack(eventType) + sensors.enableLog(false) + sensors.enableTrackGPSLocation(false) + sensors.enableTrackScreenOrientation(false) + sensors.login(getUserId()) + sensors.trackAppCrash() } static func configureSensors() { @@ -57,6 +59,19 @@ class SensorsAnalytics { UserDefaults.standard.set(userId, forKey: UserDefaultsKey.userId.rawValue) return userId } + + private func getIpId() -> String { + if let ipId = UserDefaults.standard.string(forKey: UserDefaultsKey.ipId.rawValue) { + return ipId + } + var ipId = "" + for _ in 0..<64 { + ipId += "\(arc4random_uniform(10))" + } + print(ipId.lengthOfBytes(using: .utf8)) + UserDefaults.standard.set(ipId, forKey: UserDefaultsKey.ipId.rawValue) + return ipId + } } extension SensorsAnalytics { From 97d3e954a3bf5539e010a3d28f249f56045487a5 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 12 Nov 2018 17:20:27 +0800 Subject: [PATCH 017/315] =?UTF-8?q?Remove=20'$ip'=E3=80=81'$device=5Fid'?= =?UTF-8?q?=20properties=20in=20sensors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Neuron/Services/Common/SensorsAnalytics.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Neuron/Services/Common/SensorsAnalytics.swift b/Neuron/Services/Common/SensorsAnalytics.swift index 9236d921..a73601b5 100644 --- a/Neuron/Services/Common/SensorsAnalytics.swift +++ b/Neuron/Services/Common/SensorsAnalytics.swift @@ -24,7 +24,7 @@ class SensorsAnalytics { sensors = SensorsAnalyticsSDK.sharedInstance(withServerURL: "https://banana.cryptape.com:8106/sa?project=production", andDebugMode: .off) #endif - sensors.registerSuperProperties(["platformType": "iOS", "ip_id": getIpId()]) + sensors.registerSuperProperties(["platformType": "iOS", "ip_id": getIpId(), "$ip": ""]) let eventType: SensorsAnalyticsAutoTrackEventType = [ .eventTypeAppStart, .eventTypeAppEnd, @@ -37,6 +37,11 @@ class SensorsAnalytics { sensors.enableTrackScreenOrientation(false) sensors.login(getUserId()) sensors.trackAppCrash() + + if var automaticProperties = sensors.value(forKey: "automaticProperties") as? [String: Any] { + automaticProperties["$device_id"] = "" + sensors.setValue(automaticProperties, forKey: "automaticProperties") + } } static func configureSensors() { From 74ab78920a5581bf18bb61db6e7889cb733bbd84 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 12 Nov 2018 17:25:11 +0800 Subject: [PATCH 018/315] Modify token profile UI --- Neuron/Sections/Transaction/Transaction.storyboard | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Neuron/Sections/Transaction/Transaction.storyboard b/Neuron/Sections/Transaction/Transaction.storyboard index cfa519e2..aeaf181c 100644 --- a/Neuron/Sections/Transaction/Transaction.storyboard +++ b/Neuron/Sections/Transaction/Transaction.storyboard @@ -806,13 +806,13 @@ - - + - - - - - + From 8b66a20c01436f074af51ccba8a8165f16aef2e8 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 12 Nov 2018 21:55:22 +0800 Subject: [PATCH 031/315] Modify hide guide page animation --- Neuron/Sections/Guide/GuideService.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Neuron/Sections/Guide/GuideService.swift b/Neuron/Sections/Guide/GuideService.swift index ab11d6ba..bbb85d6d 100644 --- a/Neuron/Sections/Guide/GuideService.swift +++ b/Neuron/Sections/Guide/GuideService.swift @@ -43,11 +43,17 @@ class GuideService { guard controller == nil else { return } let guideController: GuideViewController = UIStoryboard(name: .guide).instantiateViewController() controller = BaseNavigationController(rootViewController: guideController) + controller?.modalPresentationStyle = .overCurrentContext UIApplication.shared.keyWindow?.rootViewController?.present(controller!, animated: true, completion: nil) } private func hideGuide() { - controller?.dismiss(animated: true, completion: nil) - controller = nil + UIView.animate(withDuration: 0.33, animations: { + let height = self.controller?.view.bounds.size.height ?? 0.0 + self.controller?.view.transform = CGAffineTransform(translationX: 0, y: height) + }, completion: { (_) in + self.controller?.dismiss(animated: false, completion: nil) + self.controller = nil + }) } } From 7597acd964b188fa1ae80d022333a7a7ad946182 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 12 Nov 2018 22:12:14 +0800 Subject: [PATCH 032/315] Modify gas price setting page --- .../Sections/Transaction/Transaction.storyboard | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Neuron/Sections/Transaction/Transaction.storyboard b/Neuron/Sections/Transaction/Transaction.storyboard index cfa519e2..e5126590 100644 --- a/Neuron/Sections/Transaction/Transaction.storyboard +++ b/Neuron/Sections/Transaction/Transaction.storyboard @@ -391,22 +391,22 @@ - + - + - + @@ -445,7 +445,7 @@ - + @@ -453,7 +453,7 @@ - + From a32fe37235777add58eb7e3e9118a87482b48ca0 Mon Sep 17 00:00:00 2001 From: cezres Date: Tue, 13 Nov 2018 10:15:25 +0800 Subject: [PATCH 033/315] Modify transition duration --- Neuron/Sections/Authentication/AuthenticationService.swift | 2 +- Neuron/Sections/Guide/GuideService.swift | 2 +- .../Transaction/TransactionConfirmViewController.swift | 6 +++--- .../Transaction/TransactionGasPriceViewController.swift | 4 ++-- Neuron/Sections/Wallet/View/InputTextViewController.swift | 4 ++-- Neuron/Sections/Wallet/WalletViewController.swift | 1 + 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Neuron/Sections/Authentication/AuthenticationService.swift b/Neuron/Sections/Authentication/AuthenticationService.swift index 89a55c45..4414b246 100644 --- a/Neuron/Sections/Authentication/AuthenticationService.swift +++ b/Neuron/Sections/Authentication/AuthenticationService.swift @@ -133,7 +133,7 @@ class AuthenticationService { func closeAuthentication() { guard let window = window else { return } - UIView.animate(withDuration: 0.4, animations: { + UIView.animate(withDuration: CATransaction.animationDuration(), animations: { window.transform = CGAffineTransform.init(translationX: 0, y: window.bounds.size.height) }, completion: { (_) in self.window = nil diff --git a/Neuron/Sections/Guide/GuideService.swift b/Neuron/Sections/Guide/GuideService.swift index bbb85d6d..6b86c078 100644 --- a/Neuron/Sections/Guide/GuideService.swift +++ b/Neuron/Sections/Guide/GuideService.swift @@ -48,7 +48,7 @@ class GuideService { } private func hideGuide() { - UIView.animate(withDuration: 0.33, animations: { + UIView.animate(withDuration: CATransaction.animationDuration(), animations: { let height = self.controller?.view.bounds.size.height ?? 0.0 self.controller?.view.transform = CGAffineTransform(translationX: 0, y: height) }, completion: { (_) in diff --git a/Neuron/Sections/Transaction/TransactionConfirmViewController.swift b/Neuron/Sections/Transaction/TransactionConfirmViewController.swift index d68649b4..7be4e3ce 100644 --- a/Neuron/Sections/Transaction/TransactionConfirmViewController.swift +++ b/Neuron/Sections/Transaction/TransactionConfirmViewController.swift @@ -46,7 +46,7 @@ class TransactionConfirmViewController: UIViewController, TransactionConfirmSend let offset = oldValue.view.bounds.size.width controller.view.transform = CGAffineTransform(translationX: offset, y: 0) containView.addSubview(controller.view) - UIView.animate(withDuration: 0.33, animations: { + UIView.animate(withDuration: CATransaction.animationDuration(), animations: { oldValue.view.transform = CGAffineTransform(translationX: -offset, y: 0) controller.view.transform = CGAffineTransform.identity }, completion: { (_) in @@ -66,7 +66,7 @@ class TransactionConfirmViewController: UIViewController, TransactionConfirmSend super.viewWillAppear(animated) backgroundView.alpha = 0.0 contentView.transform = CGAffineTransform(translationX: 0, y: contentView.bounds.size.height) - UIView.animate(withDuration: 0.33) { + UIView.animate(withDuration: CATransaction.animationDuration()) { self.backgroundView.alpha = 1.0 self.contentView.transform = CGAffineTransform.identity } @@ -79,7 +79,7 @@ class TransactionConfirmViewController: UIViewController, TransactionConfirmSend } @IBAction func dismiss() { - UIView.animate(withDuration: 0.33, animations: { + UIView.animate(withDuration: CATransaction.animationDuration(), animations: { self.backgroundView.alpha = 0.0 self.contentView.transform = CGAffineTransform(translationX: 0, y: self.contentView.bounds.size.height) }, completion: { (_) in diff --git a/Neuron/Sections/Transaction/TransactionGasPriceViewController.swift b/Neuron/Sections/Transaction/TransactionGasPriceViewController.swift index 40724dbe..ec16a892 100644 --- a/Neuron/Sections/Transaction/TransactionGasPriceViewController.swift +++ b/Neuron/Sections/Transaction/TransactionGasPriceViewController.swift @@ -41,14 +41,14 @@ class TransactionGasPriceViewController: UIViewController { super.viewWillAppear(animated) backgroundView.alpha = 0.0 contentView.transform = CGAffineTransform(translationX: 0, y: contentView.bounds.size.height) - UIView.animate(withDuration: 0.33) { + UIView.animate(withDuration: CATransaction.animationDuration()) { self.backgroundView.alpha = 1.0 self.contentView.transform = CGAffineTransform.identity } } @IBAction func dismiss() { - UIView.animate(withDuration: 0.33, animations: { + UIView.animate(withDuration: CATransaction.animationDuration(), animations: { self.backgroundView.alpha = 0.0 self.contentView.transform = CGAffineTransform(translationX: 0, y: self.contentView.bounds.size.height) }, completion: { (_) in diff --git a/Neuron/Sections/Wallet/View/InputTextViewController.swift b/Neuron/Sections/Wallet/View/InputTextViewController.swift index 418b7246..f7a43077 100644 --- a/Neuron/Sections/Wallet/View/InputTextViewController.swift +++ b/Neuron/Sections/Wallet/View/InputTextViewController.swift @@ -40,7 +40,7 @@ class InputTextViewController: UIViewController { super.viewWillAppear(animated) backgroundView.alpha = 0.0 contentView.transform = CGAffineTransform(translationX: 0, y: contentView.bounds.size.height) - UIView.animate(withDuration: 0.33, animations: { + UIView.animate(withDuration: CATransaction.animationDuration(), animations: { self.backgroundView.alpha = 1.0 self.contentView.transform = CGAffineTransform.identity }, completion: { (_) in @@ -63,7 +63,7 @@ class InputTextViewController: UIViewController { } func dismiss() { - UIView.animate(withDuration: 0.33, animations: { + UIView.animate(withDuration: CATransaction.animationDuration(), animations: { self.backgroundView.alpha = 0.0 self.contentView.transform = CGAffineTransform(translationX: 0, y: self.contentView.bounds.size.height) }, completion: { (_) in diff --git a/Neuron/Sections/Wallet/WalletViewController.swift b/Neuron/Sections/Wallet/WalletViewController.swift index 46569de6..7947ebe4 100644 --- a/Neuron/Sections/Wallet/WalletViewController.swift +++ b/Neuron/Sections/Wallet/WalletViewController.swift @@ -60,6 +60,7 @@ class WalletViewController: UITableViewController, SelectWalletControllerDelegat } tabbedButtonView.buttonTitles = ["代币", "藏品"] tabbedButtonView.delegate = self + UIViewControllerAnimatedTransitioning } @objc private func endRefresh() { From b463caf2b2728cc4516de11c3a89c5fd4dcdf7f6 Mon Sep 17 00:00:00 2001 From: cezres Date: Tue, 13 Nov 2018 10:15:54 +0800 Subject: [PATCH 034/315] Modify wallet qrcode page --- Neuron/Sections/Wallet/RequestPayment.storyboard | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neuron/Sections/Wallet/RequestPayment.storyboard b/Neuron/Sections/Wallet/RequestPayment.storyboard index b3f4bc91..ea003e1e 100644 --- a/Neuron/Sections/Wallet/RequestPayment.storyboard +++ b/Neuron/Sections/Wallet/RequestPayment.storyboard @@ -24,7 +24,7 @@ - + @@ -119,7 +119,7 @@ - + From de85c88304ecefeeb15aa110d41b72f2f17463ea Mon Sep 17 00:00:00 2001 From: cezres Date: Tue, 13 Nov 2018 10:45:46 +0800 Subject: [PATCH 035/315] Delete test code --- Neuron/Sections/Wallet/WalletViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Neuron/Sections/Wallet/WalletViewController.swift b/Neuron/Sections/Wallet/WalletViewController.swift index 7947ebe4..46569de6 100644 --- a/Neuron/Sections/Wallet/WalletViewController.swift +++ b/Neuron/Sections/Wallet/WalletViewController.swift @@ -60,7 +60,6 @@ class WalletViewController: UITableViewController, SelectWalletControllerDelegat } tabbedButtonView.buttonTitles = ["代币", "藏品"] tabbedButtonView.delegate = self - UIViewControllerAnimatedTransitioning } @objc private func endRefresh() { From dd63b158d5b3fd762b254d3ce8fbb9ae8565977c Mon Sep 17 00:00:00 2001 From: James Chen Date: Tue, 13 Nov 2018 14:43:35 +0900 Subject: [PATCH 036/315] Delete TransactionParamBuilderDelegate --- Neuron/Sections/Dapp/ContractController.swift | 6 ++---- .../Transaction/TransactionParamBuilder.swift | 17 +---------------- .../Transaction/TransactionViewController.swift | 9 ++------- 3 files changed, 5 insertions(+), 27 deletions(-) diff --git a/Neuron/Sections/Dapp/ContractController.swift b/Neuron/Sections/Dapp/ContractController.swift index bb226722..8fcba320 100644 --- a/Neuron/Sections/Dapp/ContractController.swift +++ b/Neuron/Sections/Dapp/ContractController.swift @@ -181,7 +181,6 @@ class ContractController: UITableViewController { let service = TransactionParamBuilder.service(with: tokenModel) service.fromAddress = WalletRealmTool.getCurrentAppModel().currentWallet!.address service.amount = Double(value) ?? 0.0 - service.delegate = self switch chainType { case .appChain: @@ -203,7 +202,8 @@ class ContractController: UITableViewController { } } -extension ContractController: TransactionParamBuilderDelegate { +// TODO: tx sent +extension ContractController { func transactionCompletion(_ transactionService: TransactionParamBuilder, result: TransactionParamBuilder.Result) { switch result { case .error: @@ -222,8 +222,6 @@ extension ContractController: TransactionParamBuilderDelegate { confirmViewController?.dismiss() navigationController?.popViewController(animated: true) } - func transactionGasCostChanged(_ transactionService: TransactionParamBuilder) { - } } extension ContractController: AdvancedViewControllerDelegate { diff --git a/Neuron/Sections/Transaction/TransactionParamBuilder.swift b/Neuron/Sections/Transaction/TransactionParamBuilder.swift index e16cf9ee..cc5d5d45 100644 --- a/Neuron/Sections/Transaction/TransactionParamBuilder.swift +++ b/Neuron/Sections/Transaction/TransactionParamBuilder.swift @@ -11,18 +11,12 @@ import AppChain import Web3swift import BigInt -protocol TransactionParamBuilderDelegate: NSObjectProtocol { - func transactionCompletion(_ transactionService: TransactionParamBuilder, result: TransactionParamBuilder.Result) - func transactionGasCostChanged(_ transactionService: TransactionParamBuilder) -} - class TransactionParamBuilder { enum Result { case error(Error) case succee(TxHash) } - weak var delegate: TransactionParamBuilderDelegate? var token: TokenModel! var fromAddress: String! var tokenBalance: Double = 0.0 @@ -38,13 +32,7 @@ class TransactionParamBuilder { gasCost = Double(result) ?? 0.0 } } - var gasCost: Double = 0.0 { - didSet { - DispatchQueue.main.async { - self.delegate?.transactionGasCostChanged(self) - } - } - } + var gasCost: Double = 0.0 var gasCostAmount: Double = 0.0 var changeGasLimitEnable = false var changeGasPriceEnable = false @@ -84,9 +72,6 @@ class TransactionParamBuilder { } func completion(result: Result) { - DispatchQueue.main.async { - self.delegate?.transactionCompletion(self, result: result) - } } } diff --git a/Neuron/Sections/Transaction/TransactionViewController.swift b/Neuron/Sections/Transaction/TransactionViewController.swift index 476880fa..8d4b91bd 100644 --- a/Neuron/Sections/Transaction/TransactionViewController.swift +++ b/Neuron/Sections/Transaction/TransactionViewController.swift @@ -10,7 +10,7 @@ import UIKit import Web3swift import EthereumAddress -class TransactionViewController: UITableViewController, TransactionParamBuilderDelegate { +class TransactionViewController: UITableViewController { // Wallet @IBOutlet weak var walletIconView: UIImageView! @IBOutlet weak var walletNameLabel: UILabel! @@ -27,7 +27,6 @@ class TransactionViewController: UITableViewController, TransactionParamBuilderD super.viewDidLoad() service = TransactionParamBuilder.service(with: token) service.fromAddress = WalletRealmTool.getCurrentAppModel().currentWallet!.address - service.delegate = self Toast.showHUD() DispatchQueue.global().async { self.service.requestGasCost() @@ -75,7 +74,7 @@ class TransactionViewController: UITableViewController, TransactionParamBuilderD amountTextField.text = "\(amount)" } - // MARK: - TransactionParamBuilderDelegate + // TODO: tx sent func transactionCompletion(_ transactionService: TransactionParamBuilder, result: TransactionParamBuilder.Result) { switch result { case .error(let error): @@ -96,10 +95,6 @@ class TransactionViewController: UITableViewController, TransactionParamBuilderD } } - func transactionGasCostChanged(_ transactionService: TransactionParamBuilder) { - gasCostLabel.text = "\(service.gasCost.clean)\(token.gasSymbol)" - } - // MARK: - UI func setupUI() { let wallet = WalletRealmTool.getCurrentAppModel().currentWallet! From 7275b32d687a8b45ccc1363467f7a89c783d475b Mon Sep 17 00:00:00 2001 From: LuFP Date: Tue, 13 Nov 2018 14:11:01 +0800 Subject: [PATCH 037/315] Fix DApp gas view animate --- Neuron.xcodeproj/project.pbxproj | 4 +-- .../Dapp/AdvancedViewController.swift | 27 +++++++++---------- Neuron/Sections/Dapp/ContractController.swift | 22 +++++++++++---- Neuron/Sections/Dapp/DAppBrowser.storyboard | 15 +++++++++++ 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index a83203ef..76272a1f 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -1653,7 +1653,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = PBL67DWM43; ENABLE_BITCODE = YES; INFOPLIST_FILE = Neuron/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -1686,7 +1686,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = PBL67DWM43; ENABLE_BITCODE = YES; INFOPLIST_FILE = Neuron/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; diff --git a/Neuron/Sections/Dapp/AdvancedViewController.swift b/Neuron/Sections/Dapp/AdvancedViewController.swift index 16dae56d..0a7d8934 100644 --- a/Neuron/Sections/Dapp/AdvancedViewController.swift +++ b/Neuron/Sections/Dapp/AdvancedViewController.swift @@ -20,6 +20,8 @@ class AdvancedViewController: UIViewController { private var inputGasPrice: String? weak var delegate: AdvancedViewControllerDelegate? + @IBOutlet weak var backgroundView: UIView! + @IBOutlet weak var contentView: UIView! @IBOutlet weak var ethereumSuggestLabel: UILabel! @IBOutlet weak var gasPriceTextField: UITextField! @IBOutlet weak var gasLabel: UILabel! @@ -29,17 +31,12 @@ class AdvancedViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) assignmentForUI() - view.frame = CGRect(x: 0, y: ScreenSize.height, width: ScreenSize.width, height: ScreenSize.height) - view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - UIView.animate(withDuration: 0.5, animations: { - self.view.frame = CGRect(x: 0, y: 0, width: ScreenSize.width, height: ScreenSize.height) - }, completion: { (_) in - self.view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5) - }) + backgroundView.alpha = 0.0 + contentView.transform = CGAffineTransform(translationX: 0, y: contentView.bounds.size.height) + UIView.animate(withDuration: CATransaction.animationDuration()) { + self.backgroundView.alpha = 0.5 + self.contentView.transform = CGAffineTransform.identity + } } override func viewDidLoad() { @@ -87,11 +84,11 @@ class AdvancedViewController: UIViewController { } func hideView() { - UIView.animate(withDuration: 0.5, animations: { - self.view.frame = CGRect(x: 0, y: ScreenSize.height, width: ScreenSize.width, height: ScreenSize.height) + UIView.animate(withDuration: CATransaction.animationDuration(), animations: { + self.backgroundView.alpha = 0.0 + self.contentView.transform = CGAffineTransform(translationX: 0, y: self.contentView.bounds.size.height) }, completion: { (_) in - self.view.backgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0) - self.view.removeFromSuperview() + self.dismiss(animated: false, completion: nil) }) } diff --git a/Neuron/Sections/Dapp/ContractController.swift b/Neuron/Sections/Dapp/ContractController.swift index 3b9549ca..d9fe75da 100644 --- a/Neuron/Sections/Dapp/ContractController.swift +++ b/Neuron/Sections/Dapp/ContractController.swift @@ -48,11 +48,25 @@ class ContractController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() title = "支付详情" - advancedViewController = storyboard!.instantiateViewController(withIdentifier: "advancedViewController") as? AdvancedViewController - advancedViewController.delegate = self getTokenModel() } + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == "AdvancedViewController" { + advancedViewController = segue.destination as? AdvancedViewController + advancedViewController.delegate = self + advancedViewController.dataString = dappCommonModel.eth?.data ?? "" + advancedViewController.gasLimit = gasLimit + } + } + + override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { + if identifier == "AdvancedViewController" { + return false + } + return true + } + func getTokenModel() { let appModel = WalletRealmTool.getCurrentAppModel() appModel.nativeTokenList.forEach { (tokenModel) in @@ -166,9 +180,7 @@ class ContractController: UITableViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if dappCommonModel.chainType == "ETH" && indexPath.section == 0 && indexPath.row == 0 { - advancedViewController.dataString = dappCommonModel.eth?.data ?? "" - advancedViewController.gasLimit = gasLimit - UIApplication.shared.keyWindow?.addSubview(advancedViewController.view) + performSegue(withIdentifier: "AdvancedViewController", sender: indexPath.row) } } diff --git a/Neuron/Sections/Dapp/DAppBrowser.storyboard b/Neuron/Sections/Dapp/DAppBrowser.storyboard index 90f0cb28..dc094b11 100644 --- a/Neuron/Sections/Dapp/DAppBrowser.storyboard +++ b/Neuron/Sections/Dapp/DAppBrowser.storyboard @@ -212,6 +212,9 @@ + + + @@ -403,6 +406,11 @@ + + + + + @@ -599,14 +607,21 @@ + + + + + + + From a0062cd8ae1447d99f839efc6ae730c70fa1ba81 Mon Sep 17 00:00:00 2001 From: LuFP Date: Tue, 13 Nov 2018 14:34:21 +0800 Subject: [PATCH 038/315] Format value label --- Neuron/Sections/Dapp/ContractController.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Neuron/Sections/Dapp/ContractController.swift b/Neuron/Sections/Dapp/ContractController.swift index d9fe75da..17a55659 100644 --- a/Neuron/Sections/Dapp/ContractController.swift +++ b/Neuron/Sections/Dapp/ContractController.swift @@ -94,16 +94,25 @@ class ContractController: UITableViewController { chainType = .appChain toLabel.text = dappCommonModel.appChain?.to value = formatScientValue(value: dappCommonModel.appChain?.value ?? "0") - valueLabel.text = value + tokenModel.symbol + valueLabel.text = value gasLabel.text = getNervosTransactionCosted(with: appChainQuota) + tokenModel.symbol totlePayLabel.text = getTotleValue(value: dappCommonModel.appChain?.value ?? "0", gas: appChainQuota) + tokenModel.symbol } else { chainType = .eth toLabel.text = dappCommonModel.eth?.to value = formatScientValue(value: dappCommonModel.eth?.value ?? "0") - valueLabel.text = value + tokenModel.symbol + valueLabel.text = value getETHGas(ethGasPirce: dappCommonModel.eth?.gasPrice?.clean, ethGasLimit: dappCommonModel.eth?.gasLimit?.clean) } + formatValueLabel(value: value) + } + + func formatValueLabel(value: String) { + let range = NSMakeRange(valueLabel.text!.lengthOfBytes(using: .utf8), tokenModel.symbol.lengthOfBytes(using: .utf8)) + valueLabel.text! += tokenModel.symbol + let attributedText = NSMutableAttributedString(attributedString: valueLabel.attributedText!) + attributedText.addAttributes([NSAttributedString.Key.font: UIFont.systemFont(ofSize: 24)], range: range) + valueLabel.attributedText = attributedText } func getNervosTransactionCosted(with quotaInput: BigUInt) -> String { From aa99404e99664f33f88d07317c8bcf99a882d843 Mon Sep 17 00:00:00 2001 From: LuFP Date: Tue, 13 Nov 2018 14:37:08 +0800 Subject: [PATCH 039/315] Remove development team --- Neuron.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 76272a1f..a83203ef 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -1653,7 +1653,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = PBL67DWM43; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = YES; INFOPLIST_FILE = Neuron/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; @@ -1686,7 +1686,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = PBL67DWM43; + DEVELOPMENT_TEAM = ""; ENABLE_BITCODE = YES; INFOPLIST_FILE = Neuron/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.0; From b7191858f735c2fec836981c15a3905529ec5bfc Mon Sep 17 00:00:00 2001 From: LuFP Date: Tue, 13 Nov 2018 15:33:10 +0800 Subject: [PATCH 040/315] Remove LYEmptyView and fix search empty view --- Neuron.xcodeproj/project.pbxproj | 6 -- .../Sections/Common/NeuronProgressView.swift | 82 ------------------- .../Sections/Dapp/SearchAppController.swift | 17 +++- .../TransactionHistoryViewController.swift | 1 - .../Sections/Wallet/NFTViewController.swift | 2 - Podfile | 1 - Podfile.lock | 6 +- 7 files changed, 14 insertions(+), 101 deletions(-) delete mode 100644 Neuron/Sections/Common/NeuronProgressView.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index a83203ef..2aba0e9a 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -156,7 +156,6 @@ 89BEBE6B20EF82B6007D2705 /* EthereumNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89BEBE6A20EF82B6007D2705 /* EthereumNetwork.swift */; }; 89BEBE7220F12647007D2705 /* CommonWebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89BEBE7120F12647007D2705 /* CommonWebViewController.swift */; }; 89C25C5A20BBA38300007EC1 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C25C5820BBA38300007EC1 /* SettingsViewController.swift */; }; - 89C25C6520BBF03700007EC1 /* NeuronProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C25C6420BBF03700007EC1 /* NeuronProgressView.swift */; }; 89C25C6D20BD421E00007EC1 /* ContractController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C25C6B20BD421E00007EC1 /* ContractController.swift */; }; 89C3BF072134E3F200872BD0 /* AddWallet.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 89C3BF062134E3F200872BD0 /* AddWallet.storyboard */; }; 89C3BF09213521DE00872BD0 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C3BF08213521DE00872BD0 /* UIView+Extension.swift */; }; @@ -370,7 +369,6 @@ 89BEBE6A20EF82B6007D2705 /* EthereumNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumNetwork.swift; sourceTree = ""; }; 89BEBE7120F12647007D2705 /* CommonWebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonWebViewController.swift; sourceTree = ""; }; 89C25C5820BBA38300007EC1 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; - 89C25C6420BBF03700007EC1 /* NeuronProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeuronProgressView.swift; sourceTree = ""; }; 89C25C6B20BD421E00007EC1 /* ContractController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContractController.swift; sourceTree = ""; }; 89C3BF062134E3F200872BD0 /* AddWallet.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = AddWallet.storyboard; sourceTree = ""; }; 89C3BF08213521DE00872BD0 /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = ""; }; @@ -741,7 +739,6 @@ isa = PBXGroup; children = ( 89812129218D3161000813C8 /* QRCodeViewController.swift */, - 89C25C6420BBF03700007EC1 /* NeuronProgressView.swift */, 8964A17F20BE94F70086848F /* NEPickerView.swift */, 1A207DD42137D8CF008DC306 /* TabbedButtonsView.swift */, 1A207DD62137D8F5008DC306 /* TabbedButtonsView.xib */, @@ -1205,7 +1202,6 @@ "${BUILT_PRODUCTS_DIR}/EthereumAddress/EthereumAddress.framework", "${BUILT_PRODUCTS_DIR}/IGIdenticon/IGIdenticon.framework", "${BUILT_PRODUCTS_DIR}/IQKeyboardManagerSwift/IQKeyboardManagerSwift.framework", - "${BUILT_PRODUCTS_DIR}/LYEmptyView/LYEmptyView.framework", "${BUILT_PRODUCTS_DIR}/PlainPing/PlainPing.framework", "${BUILT_PRODUCTS_DIR}/PromiseKit/PromiseKit.framework", "${BUILT_PRODUCTS_DIR}/PullToRefresher/PullToRefresh.framework", @@ -1234,7 +1230,6 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/EthereumAddress.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IGIdenticon.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/IQKeyboardManagerSwift.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/LYEmptyView.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PlainPing.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PromiseKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/PullToRefresh.framework", @@ -1358,7 +1353,6 @@ 8928C24520AE90C000C3103E /* BaseNavigationController.swift in Sources */, 6E83CB8E216F573100029324 /* GuideService.swift in Sources */, 8964A18020BE94F70086848F /* NEPickerView.swift in Sources */, - 89C25C6520BBF03700007EC1 /* NeuronProgressView.swift in Sources */, 1AB3D1CF21709F6700557E9D /* UIStoryboard+Extension.swift in Sources */, 899AD19B211011F300A8AC09 /* AppChainNetwork.swift in Sources */, 1A207DD52137D8CF008DC306 /* TabbedButtonsView.swift in Sources */, diff --git a/Neuron/Sections/Common/NeuronProgressView.swift b/Neuron/Sections/Common/NeuronProgressView.swift deleted file mode 100644 index 99c13ac3..00000000 --- a/Neuron/Sections/Common/NeuronProgressView.swift +++ /dev/null @@ -1,82 +0,0 @@ -// -// NeuronProgressView.swift -// Neuron -// -// Created by XiaoLu on 2018/5/28. -// Copyright © 2018年 cryptape. All rights reserved. -// - -import UIKit - -// # 这个代理协议是为了在进度条改变的时候 让其代理执行方法 例如音频根据改变的进度去 相应的调整到对应的位置播放 -protocol NeuronProgressViewDelegate: class { - func NProgressView(neuronProgressView: UIProgressView, changeProgress currenProgress: Float) - -} - -class NeuronProgressView: UIView { - - let progressV = UIProgressView.init() - - // 进度条上的滑块 - let sliderView: UIView = UIView.init() - // 代理 - weak var delegate: NeuronProgressViewDelegate? - - // 代码初始化方法 - override init(frame: CGRect) { - super.init(frame: frame) - - // 设置一下 滑块的大小 位置 颜色 等属性 - sliderView.backgroundColor = ColorFromString(hex: "#2e4af2") - sliderView.bounds = CGRect(x: 0, y: 0, width: 20, height: 20) - sliderView.layer.cornerRadius = 10 - sliderView.clipsToBounds = true - // 让小滑块的中心点 在进度的端点位置 - sliderView.center = CGPoint(x: CGFloat(progressV.progress) * progressV.bounds.size.width, y: progressV.bounds.size.height / 2) - progressV.addSubview(sliderView) - - progressV.frame = CGRect(x: 0, y: 5, width: frame.size.width, height: 5) - self.addSubview(progressV) - - // 给滑条加一个手势 目的是为了点击滑条 进度端点就变成点击点位置 - self.isUserInteractionEnabled = true // 开交互 - let tap = UIPanGestureRecognizer.init(target: self, action: #selector(tapAction(tap:))) - self.addGestureRecognizer(tap) - } - // 可视化编程 初始化 - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - // 点击进度条后执行的 手势事件 - @objc func tapAction(tap: UIPanGestureRecognizer) { - let touchPoint: CGPoint = tap.location(in: self) - // 设置进度条的进度 当前点击的点前面的长度 占整个宽度的百分比 就是当前的进度 - progressV.setProgress(Float(touchPoint.x / progressV.bounds.size.width), animated: true) - addAnimation(point: touchPoint) - // 进度条改变了 出发代理执行代理事件 让用的地方可以相应的改变 比如音频视频的播放进度调整 - self.delegate?.NProgressView(neuronProgressView: progressV, changeProgress: progressV.progress) - - } - - // 重新把 子控件的滑块 布局到端点位置 - override func layoutSubviews() { - super.layoutSubviews() - // 让小滑块的中心点 在进度的端点位置 - sliderView.center = CGPoint(x: CGFloat(progressV.progress) * self.bounds.size.width, y: progressV.bounds.size.height / 2) - } - - //计算移动 - func addAnimation(point: CGPoint) { - if 0 >= progressV.frame.origin.x && point.x <= progressV.frame.size.width { - sliderView.ly_origin.x = point.x - } - } - - //结束之后和正在的时候调用代理 - func didCallDelegate(viewP: CGPoint) { - delegate?.NProgressView(neuronProgressView: progressV, changeProgress: Float(viewP.x/progressV.center.x)) - } - -} diff --git a/Neuron/Sections/Dapp/SearchAppController.swift b/Neuron/Sections/Dapp/SearchAppController.swift index bd1f4464..7255b46f 100644 --- a/Neuron/Sections/Dapp/SearchAppController.swift +++ b/Neuron/Sections/Dapp/SearchAppController.swift @@ -7,21 +7,20 @@ // import UIKit -import LYEmptyView -class SearchAppController: UITableViewController { +class SearchAppController: UITableViewController, ErrorOverlayPresentable { @IBOutlet var textField: UITextField! var searchHistorys: [String] = [] override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(false, animated: true) + setUpTableView() } override func viewDidLoad() { super.viewDidLoad() setUpNavigationTitleView() - setUpTableView() } func setUpNavigationTitleView() { @@ -42,7 +41,12 @@ class SearchAppController: UITableViewController { func setUpTableView() { searchHistorys = UserDefaults.standard.object(forKey: "searchHistory") as? [String] ?? [] tableView.tableFooterView = UIView() - tableView.ly_emptyView = LYEmptyView.empty(withImageStr: "emptyData", titleStr: "您还没有搜索记录", detailStr: "") + if searchHistorys.count == 0 { + errorOverlaycontroller.style = .blank + showOverlay() + } else { + removeOverlay() + } tableView.reloadData() } @@ -105,7 +109,12 @@ class SearchAppController: UITableViewController { self.searchHistorys.remove(at: indexPath.row) UserDefaults.standard.set(searchHistorys, forKey: "searchHistory") tableView.reloadData() + if self.searchHistorys.count == 0 { + errorOverlaycontroller.style = .blank + showOverlay() + } } + } override func tableView(_ tableView: UITableView, titleForDeleteConfirmationButtonForRowAt indexPath: IndexPath) -> String? { diff --git a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift index 41531889..dc89d081 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift @@ -7,7 +7,6 @@ // import UIKit -import LYEmptyView import PullToRefresh import WebKit diff --git a/Neuron/Sections/Wallet/NFTViewController.swift b/Neuron/Sections/Wallet/NFTViewController.swift index 13c7e50c..af81be98 100644 --- a/Neuron/Sections/Wallet/NFTViewController.swift +++ b/Neuron/Sections/Wallet/NFTViewController.swift @@ -7,12 +7,10 @@ // import UIKit -import LYEmptyView /// ERC-721 List class NFTViewController: UITableViewController, ErrorOverlayPresentable { var dataArray: [AssetsModel] = [] - private let testAddress = "0xac30bce77cf849d869aa37e39b983fa50767a2dd" override func viewDidLoad() { super.viewDidLoad() diff --git a/Podfile b/Podfile index ee9a0f37..fc75673d 100644 --- a/Podfile +++ b/Podfile @@ -13,7 +13,6 @@ target 'Neuron' do pod 'SensorsAnalyticsSDK', git: "https://github.com/sensorsdata/sa-sdk-ios", branch: "v1.10.15" pod 'PlainPing' - pod 'LYEmptyView' pod 'SDWebImage' pod 'IQKeyboardManagerSwift' pod 'EFQRCode' diff --git a/Podfile.lock b/Podfile.lock index d02d2aa8..d5e7269c 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -18,7 +18,6 @@ PODS: - CryptoSwift (~> 0.13) - IGIdenticon (0.6) - IQKeyboardManagerSwift (6.2.0) - - LYEmptyView (1.2.2) - PlainPing (0.5) - PromiseKit (6.5.2): - PromiseKit/CorePromise (= 6.5.2) @@ -67,7 +66,6 @@ DEPENDENCIES: - EFQRCode - IGIdenticon (~> 0.6) - IQKeyboardManagerSwift - - LYEmptyView - PlainPing - PullToRefresher (~> 3.1) - QRCodeReader.swift @@ -88,7 +86,6 @@ SPEC REPOS: - EthereumAddress - IGIdenticon - IQKeyboardManagerSwift - - LYEmptyView - PlainPing - PromiseKit - PullToRefresher @@ -131,7 +128,6 @@ SPEC CHECKSUMS: EthereumAddress: f476e1320dca3a0024431e713ede7a09c7eb7796 IGIdenticon: 5790befde4fe56296927c72c0efed3d07b21de8e IQKeyboardManagerSwift: b07ccf9d8cafe993dcd6cb794eb4ba34611a0c4e - LYEmptyView: 12c9db1f3946fa3c812674467ad95b7ae7c93614 PlainPing: 6b248b94d92be7ec0410952d1b09af43b9a39015 PromiseKit: 27c1601bfb73405871b805bcb8cf7e55c4dad3db PullToRefresher: ec8c1942bd61bb512ff520c49f9f00903cfa86c7 @@ -149,6 +145,6 @@ SPEC CHECKSUMS: Toast-Swift: 594b5c5e5129f15438e410207e43287f027b3c00 web3swift: 33f30ca0e061e0f89117dfb46f5ee9f626eff6d6 -PODFILE CHECKSUM: 269ee06fe70f34b43d27938fb94c91addde4b51a +PODFILE CHECKSUM: 7addb053d8935593383989ad172214d6f76a0f1a COCOAPODS: 1.5.3 From d838ac130a998e40158c8726e8035f40d7f045cd Mon Sep 17 00:00:00 2001 From: cezres Date: Tue, 13 Nov 2018 16:10:01 +0800 Subject: [PATCH 041/315] Fix swipe back --- .../Sections/Dapp/BrowserViewController.swift | 3 ++ Neuron/Utils/FixSwipeBackable.swift | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 Neuron/Utils/FixSwipeBackable.swift diff --git a/Neuron/Sections/Dapp/BrowserViewController.swift b/Neuron/Sections/Dapp/BrowserViewController.swift index aff47928..bb8f6e51 100644 --- a/Neuron/Sections/Dapp/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/BrowserViewController.swift @@ -68,6 +68,9 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable { guard let url = self?.mainUrl else { return } self?.webView.load(URLRequest(url: url)) } + // fix swipe back + let gestureRecognizer = fixSwipeBack() + webView.addGestureRecognizer(gestureRecognizer) } func getRequestStr(requestStr: String) -> String { diff --git a/Neuron/Utils/FixSwipeBackable.swift b/Neuron/Utils/FixSwipeBackable.swift new file mode 100644 index 00000000..d5d5a4dc --- /dev/null +++ b/Neuron/Utils/FixSwipeBackable.swift @@ -0,0 +1,30 @@ +// +// FixSwipeBackable.swift +// Neuron +// +// Created by 晨风 on 2018/11/13. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit + +protocol FixSwipeBackable: UIGestureRecognizerDelegate { +} + +extension FixSwipeBackable where Self: UIViewController { + func fixSwipeBack() -> UIPanGestureRecognizer { + let internalTargets = navigationController?.interactivePopGestureRecognizer?.value(forKey: "targets") as! [NSObject] + let internalTarget = internalTargets.first?.value(forKey: "target") + let internalAction = NSSelectorFromString("handleNavigationTransition:") + let panGestureRecognizer = UIPanGestureRecognizer(target: internalTarget, action: internalAction) + panGestureRecognizer.delegate = self + view.addGestureRecognizer(panGestureRecognizer) + return panGestureRecognizer + } + + func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + guard let gestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer else { return true } + let translation = gestureRecognizer.translation(in: view) + return translation.x > 0 + } +} From b54f9a54ec342adff237ae78916198154e692e39 Mon Sep 17 00:00:00 2001 From: cezres Date: Tue, 13 Nov 2018 16:10:32 +0800 Subject: [PATCH 042/315] Modify icons --- .../Icons/close.imageset/Contents.json | 3 +-- .../Icons/close.imageset/close@2x.png | Bin 614 -> 0 bytes .../Icons/close.imageset/close@3x.png | Bin 812 -> 0 bytes .../\345\205\263\351\227\255 (1).png" | Bin 0 -> 1561 bytes .../nav_close.imageset/Contents.json | 21 ++++++++++++++++++ .../\345\205\263\351\227\255 (1).png" | Bin 0 -> 1561 bytes 6 files changed, 22 insertions(+), 2 deletions(-) delete mode 100644 Neuron/Assets.xcassets/Icons/close.imageset/close@2x.png delete mode 100644 Neuron/Assets.xcassets/Icons/close.imageset/close@3x.png create mode 100644 "Neuron/Assets.xcassets/Icons/close.imageset/\345\205\263\351\227\255 (1).png" create mode 100644 Neuron/Assets.xcassets/NavigationBar/nav_close.imageset/Contents.json create mode 100644 "Neuron/Assets.xcassets/NavigationBar/nav_close.imageset/\345\205\263\351\227\255 (1).png" diff --git a/Neuron/Assets.xcassets/Icons/close.imageset/Contents.json b/Neuron/Assets.xcassets/Icons/close.imageset/Contents.json index 62fe1caa..3ee82686 100644 --- a/Neuron/Assets.xcassets/Icons/close.imageset/Contents.json +++ b/Neuron/Assets.xcassets/Icons/close.imageset/Contents.json @@ -6,12 +6,11 @@ }, { "idiom" : "universal", - "filename" : "close@2x.png", + "filename" : "关闭 (1).png", "scale" : "2x" }, { "idiom" : "universal", - "filename" : "close@3x.png", "scale" : "3x" } ], diff --git a/Neuron/Assets.xcassets/Icons/close.imageset/close@2x.png b/Neuron/Assets.xcassets/Icons/close.imageset/close@2x.png deleted file mode 100644 index befdf8e6fa553ab979b415cb7bb8f426d2d9583b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 614 zcmV-s0-61ZP)Px%AW1|)R9Fek*h@~sFc1ddv60F-xDJm+;TnZS#R}GCg;*4>L0MFA9nJv~HU5V> za`W(GY?@}#q=`NAWs+3WsM&n6RAkN->Dlt>?t>0sxtTwl8rr-N>9q#V7&^S@Zh!!cfehhb+5zqx8N>Ag_k~Qs4Fdlg znTiVnJIL*mNl-dB87NIu zTKz0wF0vGdeA&QMWDT4c7(>>=6#<8k*pIf2n~sFvIUXPQ5M5$FUZ-02U@aW-6ax{& zM{>{#l|vjX`fpX?(A>e@JcFneC zXnbLU&9?k{ZQluh8ckEz&$Z8NpWk7C1t!>j0f2YO?ySuHdjJ3c07*qoM6N<$f@tX( A=Kufz diff --git a/Neuron/Assets.xcassets/Icons/close.imageset/close@3x.png b/Neuron/Assets.xcassets/Icons/close.imageset/close@3x.png deleted file mode 100644 index 394091370ee9db6eb8e4ad02e540bf06a1cebe84..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 812 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 Date: Tue, 13 Nov 2018 16:18:45 +0800 Subject: [PATCH 043/315] Update `UIControl+ExpandArea` --- .../UIKit/UIControl+Extension.swift | 69 ++++++++++++++++--- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/Neuron/Extensions/UIKit/UIControl+Extension.swift b/Neuron/Extensions/UIKit/UIControl+Extension.swift index 9be0ef27..038c35ef 100644 --- a/Neuron/Extensions/UIKit/UIControl+Extension.swift +++ b/Neuron/Extensions/UIKit/UIControl+Extension.swift @@ -10,27 +10,80 @@ import UIKit extension UIControl { private static var expandAreaAssiciationKey: Int = 0 + private static var expandAreaInsetsAssiciationKey: Int = 0 @IBInspectable var expandArea: CGFloat { set { - objc_setAssociatedObject(self, &UIControl.expandAreaAssiciationKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + expandAreaInsets = UIEdgeInsets(top: newValue, left: newValue, bottom: newValue, right: newValue) } get { - guard let insetsValue = objc_getAssociatedObject(self, &UIControl.expandAreaAssiciationKey) as? NSNumber else { return 0.0 } - return CGFloat(insetsValue.doubleValue) + return expandAreaInsets.left + } + } + var expandAreaInsets: UIEdgeInsets { + set { + objc_setAssociatedObject(self, &UIControl.expandAreaInsetsAssiciationKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + get { + guard let insetsValue = objc_getAssociatedObject(self, &UIControl.expandAreaInsetsAssiciationKey) else { return UIEdgeInsets.zero } + guard let insets = insetsValue as? UIEdgeInsets else { return UIEdgeInsets.zero } + return insets + } + } + @IBInspectable var expandLeft: CGFloat { + set { + let insets = expandAreaInsets + expandAreaInsets = UIEdgeInsets(top: insets.top, left: newValue, bottom: insets.bottom, right: insets.right) + } + get { + return expandAreaInsets.left + } + } + @IBInspectable var expandRight: CGFloat { + set { + let insets = expandAreaInsets + expandAreaInsets = UIEdgeInsets(top: insets.top, left: insets.left, bottom: insets.bottom, right: newValue) + } + get { + return expandAreaInsets.right + } + } + @IBInspectable var expandTop: CGFloat { + set { + let insets = expandAreaInsets + expandAreaInsets = UIEdgeInsets(top: newValue, left: insets.left, bottom: insets.bottom, right: insets.right) + } + get { + return expandAreaInsets.top + } + } + @IBInspectable var expandBottom: CGFloat { + set { + let insets = expandAreaInsets + expandAreaInsets = UIEdgeInsets(top: insets.top, left: insets.left, bottom: newValue, right: insets.right) + } + get { + return expandAreaInsets.bottom + } + } + + open override func didMoveToSuperview() { + super.didMoveToSuperview() + if expandAreaInsets != UIEdgeInsets.zero { } } open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard !isHidden else { return nil } - guard expandArea > 0 else { + let insets = expandAreaInsets + guard insets != UIEdgeInsets.zero else { return super.hitTest(point, with: event) } let expandRect = CGRect( - x: bounds.origin.x - expandArea, - y: bounds.origin.y - expandArea, - width: bounds.size.width + expandArea * 2, - height: bounds.size.height + expandArea * 2 + x: bounds.origin.x - insets.left, + y: bounds.origin.y - insets.top, + width: bounds.size.width + insets.left + insets.right, + height: bounds.size.height + insets.top + insets.bottom ) return expandRect.contains(point) ? self : nil } From e7e612771a986b869826f2cde29e5b24869847c4 Mon Sep 17 00:00:00 2001 From: cezres Date: Tue, 13 Nov 2018 16:20:36 +0800 Subject: [PATCH 044/315] Fix navigation item space --- Neuron.xcodeproj/project.pbxproj | 16 +++++++ Neuron/AppDelegate.swift | 2 + .../Foundation/NSObject+Extension.swift | 17 +++++++ .../UIKit/UINavigationBar+FixSpace.swift | 27 ++++++++++++ .../UIKit/UINavigationItem+FixSpace.swift | 44 +++++++++++++++++++ .../Sections/Dapp/BrowserViewController.swift | 6 +-- Neuron/Sections/Dapp/DAppBrowser.storyboard | 19 +++++--- 7 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 Neuron/Extensions/Foundation/NSObject+Extension.swift create mode 100644 Neuron/Extensions/UIKit/UINavigationBar+FixSpace.swift create mode 100644 Neuron/Extensions/UIKit/UINavigationItem+FixSpace.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index a83203ef..ac73fd1b 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -60,6 +60,10 @@ 6E9D7CBE216B386F0044176D /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9D7CBC216B386F0044176D /* AuthenticationViewController.swift */; }; 6E9D7CC1216B3AE40044176D /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E9D7CC0216B3AE40044176D /* Authentication.storyboard */; }; 6E9D7CC5216B490D0044176D /* AuthDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9D7CC4216B490D0044176D /* AuthDeviceViewController.swift */; }; + 6E9F9D56219AA71F009ED8B2 /* UINavigationBar+FixSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9F9D55219AA71F009ED8B2 /* UINavigationBar+FixSpace.swift */; }; + 6E9F9D58219AAF78009ED8B2 /* NSObject+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9F9D57219AAF78009ED8B2 /* NSObject+Extension.swift */; }; + 6E9F9D5A219AAF99009ED8B2 /* UINavigationItem+FixSpace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9F9D59219AAF99009ED8B2 /* UINavigationItem+FixSpace.swift */; }; + 6E9F9D5F219ABDE7009ED8B2 /* FixSwipeBackable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9F9D5E219ABDE7009ED8B2 /* FixSwipeBackable.swift */; }; 6EABFB0B21991BC200305ED5 /* DAppQRCodeMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EABFB0A21991BC200305ED5 /* DAppQRCodeMessageHandler.swift */; }; 6EABFB0D21991CB300305ED5 /* DAppNativeMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EABFB0C21991CB300305ED5 /* DAppNativeMessageHandler.swift */; }; 6EABFB10219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EABFB0F219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift */; }; @@ -266,6 +270,10 @@ 6E9D7CBC216B386F0044176D /* AuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = ""; }; 6E9D7CC0216B3AE40044176D /* Authentication.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Authentication.storyboard; sourceTree = ""; }; 6E9D7CC4216B490D0044176D /* AuthDeviceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthDeviceViewController.swift; sourceTree = ""; }; + 6E9F9D55219AA71F009ED8B2 /* UINavigationBar+FixSpace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+FixSpace.swift"; sourceTree = ""; }; + 6E9F9D57219AAF78009ED8B2 /* NSObject+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSObject+Extension.swift"; sourceTree = ""; }; + 6E9F9D59219AAF99009ED8B2 /* UINavigationItem+FixSpace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationItem+FixSpace.swift"; sourceTree = ""; }; + 6E9F9D5E219ABDE7009ED8B2 /* FixSwipeBackable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FixSwipeBackable.swift; sourceTree = ""; }; 6EABFB0A21991BC200305ED5 /* DAppQRCodeMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppQRCodeMessageHandler.swift; sourceTree = ""; }; 6EABFB0C21991CB300305ED5 /* DAppNativeMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppNativeMessageHandler.swift; sourceTree = ""; }; 6EABFB0F219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppDeviceMotionMessageHandler.swift; sourceTree = ""; }; @@ -505,6 +513,7 @@ 6E876844218031670032EBCE /* UInt+Extension.swift */, 1A3C652B212EAC4800F1A0AA /* String+Extension.swift */, 892A1984215A55F400B2293D /* Double+Extension.swift */, + 6E9F9D57219AAF78009ED8B2 /* NSObject+Extension.swift */, ); path = Foundation; sourceTree = ""; @@ -515,6 +524,8 @@ 89C3BF08213521DE00872BD0 /* UIView+Extension.swift */, 1AB3D1CE21709F6700557E9D /* UIStoryboard+Extension.swift */, 6EF8F40B2175CAD9004B7587 /* UIControl+Extension.swift */, + 6E9F9D55219AA71F009ED8B2 /* UINavigationBar+FixSpace.swift */, + 6E9F9D59219AAF99009ED8B2 /* UINavigationItem+FixSpace.swift */, ); path = UIKit; sourceTree = ""; @@ -764,6 +775,7 @@ 1A69465F2192A0B0000093A2 /* GasCalculator.swift */, 6E867E0C216C92A900BD6FE5 /* OverlayPresentable.swift */, 6E859CB72176DC3100637E1C /* Overlay.storyboard */, + 6E9F9D5E219ABDE7009ED8B2 /* FixSwipeBackable.swift */, ); path = Utils; sourceTree = ""; @@ -1341,6 +1353,7 @@ 6E8168F921887A31007641BA /* TransactionGasPriceViewController.swift in Sources */, 6E83CB8D216F573100029324 /* ProductAgreementViewController.swift in Sources */, 8905660720D0AC120041D4B4 /* AppModel.swift in Sources */, + 6E9F9D56219AA71F009ED8B2 /* UINavigationBar+FixSpace.swift in Sources */, 6E851CD2217DBB3D00769E00 /* TransactionHistoryService.swift in Sources */, 898A1A0F20B6539800ECB465 /* AddAssetTableViewCell.swift in Sources */, 89CA1FAE213F832800669B2C /* CurrencyService.swift in Sources */, @@ -1360,6 +1373,7 @@ 8964A18020BE94F70086848F /* NEPickerView.swift in Sources */, 89C25C6520BBF03700007EC1 /* NeuronProgressView.swift in Sources */, 1AB3D1CF21709F6700557E9D /* UIStoryboard+Extension.swift in Sources */, + 6E9F9D5F219ABDE7009ED8B2 /* FixSwipeBackable.swift in Sources */, 899AD19B211011F300A8AC09 /* AppChainNetwork.swift in Sources */, 1A207DD52137D8CF008DC306 /* TabbedButtonsView.swift in Sources */, 8928C27A20B2A61900C3103E /* WalletViewController.swift in Sources */, @@ -1372,6 +1386,7 @@ 89D84CE121475755006B0287 /* NFTService.swift in Sources */, 898A1A2A20B7F0FC00ECB465 /* TradeDetailsController.swift in Sources */, 6E876843218023520032EBCE /* CoinMarketCap.swift in Sources */, + 6E9F9D58219AAF78009ED8B2 /* NSObject+Extension.swift in Sources */, 898A19EC20B5018B00ECB465 /* AssetsDetailController.swift in Sources */, 89A196192175D9C800BD5FA4 /* DAppDataHandle.swift in Sources */, 89D84CEB2148C03B006B0287 /* NFTHeaderReusableView.swift in Sources */, @@ -1391,6 +1406,7 @@ 898A1A0B20B64F6000ECB465 /* AddAssetController.swift in Sources */, 89F9E73E20DEBDE8009E68D4 /* ExportKeystoreController.swift in Sources */, 89D935252139363A002FAEEC /* TokenTableViewCell.swift in Sources */, + 6E9F9D5A219AAF99009ED8B2 /* UINavigationItem+FixSpace.swift in Sources */, 89C614602141421400DC3DF4 /* TransactionHistoryViewController.swift in Sources */, 8913437820CA56CA00A17AEF /* RealmHelper.swift in Sources */, 8955580520DCA7F700FC92D8 /* PasswordValidator.swift in Sources */, diff --git a/Neuron/AppDelegate.swift b/Neuron/AppDelegate.swift index b8e858b4..6665289c 100644 --- a/Neuron/AppDelegate.swift +++ b/Neuron/AppDelegate.swift @@ -27,6 +27,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { GuideService.shared.register() AuthenticationService.shared.register() SensorsAnalytics.configureSensors() + UINavigationBar.fixSpace + UINavigationItem.fixSpace return true } diff --git a/Neuron/Extensions/Foundation/NSObject+Extension.swift b/Neuron/Extensions/Foundation/NSObject+Extension.swift new file mode 100644 index 00000000..418f71f8 --- /dev/null +++ b/Neuron/Extensions/Foundation/NSObject+Extension.swift @@ -0,0 +1,17 @@ +// +// NSObject+Extension.swift +// Neuron +// +// Created by 晨风 on 2018/11/13. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import Foundation + +extension NSObject { + static func swizzedMethod(originalSelector: Selector, swizzledSelector: Selector) { + guard let originalMethod = class_getInstanceMethod(self.classForCoder(), originalSelector) else { return } + guard let swizzledMethod = class_getInstanceMethod(self.classForCoder(), swizzledSelector) else { return } + method_exchangeImplementations(originalMethod, swizzledMethod) + } +} diff --git a/Neuron/Extensions/UIKit/UINavigationBar+FixSpace.swift b/Neuron/Extensions/UIKit/UINavigationBar+FixSpace.swift new file mode 100644 index 00000000..eb4bf886 --- /dev/null +++ b/Neuron/Extensions/UIKit/UINavigationBar+FixSpace.swift @@ -0,0 +1,27 @@ +// +// UINavigationBar+FixSpace.swift +// Neuron +// +// Created by 晨风 on 2018/11/13. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit + +extension UINavigationBar { + static let fixSpace: Void = { + if #available(iOS 11.0, *) { + swizzedMethod(originalSelector: #selector(layoutSubviews), swizzledSelector: #selector(swizzle_layoutSubviews)) + } + }() + + @objc func swizzle_layoutSubviews() { + swizzle_layoutSubviews() + layoutMargins = .zero + for view in subviews { + if NSStringFromClass(view.classForCoder).contains("ContentView") { + view.layoutMargins = UIEdgeInsets.zero + } + } + } +} diff --git a/Neuron/Extensions/UIKit/UINavigationItem+FixSpace.swift b/Neuron/Extensions/UIKit/UINavigationItem+FixSpace.swift new file mode 100644 index 00000000..83f17b66 --- /dev/null +++ b/Neuron/Extensions/UIKit/UINavigationItem+FixSpace.swift @@ -0,0 +1,44 @@ +// +// UINavigationItem+FixSpace.swift +// Neuron +// +// Created by 晨风 on 2018/11/13. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit + +extension UINavigationItem { + static let fixSpace: Void = { + swizzedMethod( + originalSelector: #selector(setLeftBarButtonItems(_:animated:)), + swizzledSelector: #selector(swizzle_setLeftBarButtonItems(_:animated:)) + ) + swizzedMethod( + originalSelector: NSSelectorFromString("setLeftBarButtonItem:"), + swizzledSelector: #selector(swizzle_setLeftBarButtonItem(_:)) + ) + }() + + @objc func swizzle_setLeftBarButtonItems(_ items: [UIBarButtonItem]?, animated: Bool) { + if var items = items { +// let version = UIDevice.current.systemVersion.components(separatedBy: ".").first! +// if Double(version)! < 11.0 { + let fixSpaceBarButtonItem = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) + fixSpaceBarButtonItem.width = -16 + items.insert(fixSpaceBarButtonItem, at: 0) +// } + swizzle_setLeftBarButtonItems(items, animated: animated) + } else { + swizzle_setLeftBarButtonItems(items, animated: animated) + } + } + + @objc func swizzle_setLeftBarButtonItem(_ item: UIBarButtonItem) { + setLeftBarButtonItems([item], animated: false) + } + + func fixSpace() { + setLeftBarButtonItems(leftBarButtonItems, animated: false) + } +} diff --git a/Neuron/Sections/Dapp/BrowserViewController.swift b/Neuron/Sections/Dapp/BrowserViewController.swift index bb8f6e51..43f12768 100644 --- a/Neuron/Sections/Dapp/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/BrowserViewController.swift @@ -9,14 +9,11 @@ import UIKit import WebKit -class BrowserViewController: UIViewController, ErrorOverlayPresentable { - @IBOutlet weak var backButton: UIButton! +class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipeBackable { @IBOutlet weak var closeButton: UIButton! - @IBOutlet weak var collectionButton: UIButton! var requestUrlStr = "" var transactionConfirmViewController: TransactionConfirmViewController? var mainUrl: URL? - lazy var webView: WKWebView = { let webView = WKWebView( frame: CGRect(x: 0, y: 0, width: ScreenSize.width, height: ScreenSize.height - 64), @@ -68,6 +65,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable { guard let url = self?.mainUrl else { return } self?.webView.load(URLRequest(url: url)) } + navigationItem.fixSpace() // fix swipe back let gestureRecognizer = fixSwipeBack() webView.addGestureRecognizer(gestureRecognizer) diff --git a/Neuron/Sections/Dapp/DAppBrowser.storyboard b/Neuron/Sections/Dapp/DAppBrowser.storyboard index 90f0cb28..e6dec415 100644 --- a/Neuron/Sections/Dapp/DAppBrowser.storyboard +++ b/Neuron/Sections/Dapp/DAppBrowser.storyboard @@ -93,16 +93,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift new file mode 100644 index 00000000..114e9b23 --- /dev/null +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -0,0 +1,191 @@ +// +// SendTransactionViewController.swift +// Neuron +// +// Created by 晨风 on 2018/10/30. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import BigInt +import AppChain +import Web3swift +import EthereumAddress + +class SendTransactionViewController: UITableViewController { + @IBOutlet weak var walletIconView: UIImageView! + @IBOutlet weak var walletNameLabel: UILabel! + @IBOutlet weak var walletAddressLabel: UILabel! + @IBOutlet weak var tokenBalanceButton: UIButton! + @IBOutlet weak var amountTextField: UITextField! + @IBOutlet weak var gasCostLabel: UILabel! + @IBOutlet weak var addressTextField: UITextField! + + private var paramBuilder: TransactionParamBuilder! + private var observers = [NSKeyValueObservation]() + var token: TokenModel! + var confirmViewController: TransactionConfirmViewController? + + override func viewDidLoad() { + super.viewDidLoad() + + paramBuilder = TransactionParamBuilder(token: token) + observers.append(paramBuilder.observe(\.txFeeNatural, options: [.initial]) { (_, _) in + self.updateGasCost() + }) + paramBuilder.from = WalletRealmTool.getCurrentAppModel().currentWallet!.address + + setupUI() + } + + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == "TransactionConfirmViewController" { + let controller = segue.destination as! TransactionConfirmViewController + controller.paramBuilder = paramBuilder + confirmViewController = controller + } else if segue.identifier == "TransactionGasPriceViewController" { + let controller = segue.destination as! TransactionGasPriceViewController + controller.param = paramBuilder + } + } + + // MARK: - Event + @IBAction func next(_ sender: Any) { + let amount = Double(amountTextField.text!) ?? 0.0 + paramBuilder.value = amount.toAmount(token.decimals) + paramBuilder.to = addressTextField.text! + + if isEffectiveTransferInfo { + performSegue(withIdentifier: "TransactionConfirmViewController", sender: nil) + } + } + + @IBAction func scanQRCode() { + UIApplication.shared.keyWindow?.endEditing(true) + let qrCodeViewController = QRCodeViewController() + qrCodeViewController.delegate = self + navigationController?.pushViewController(qrCodeViewController, animated: true) + } + + @IBAction func transactionAvailableBalance() { + // TODO: FIXME: erc20 token requires ETH balance for tx fee + let amount = Double(token.tokenBalance)! - paramBuilder.txFeeNatural + amountTextField.text = "\(amount)" + paramBuilder.value = amount.toAmount(token.decimals) + guard paramBuilder.hasSufficientBalance else { + Toast.showToast(text: "请确保账户剩余\(token.gasSymbol)高于矿工费用,以便顺利完成转账~") + return + } + } + + // TODO: tx sent + /* + func transactionCompletion(_ transactionService: TransactionParamBuilder, result: TransactionParamBuilder.Result) { + switch result { + case .error(let error): + Toast.showToast(text: error.localizedDescription) + default: + Toast.showToast(text: "转账成功,请稍后刷新查看") + confirmViewController?.dismiss() + navigationController?.popViewController(animated: true) + + SensorsAnalytics.Track.transaction( + chainType: token.chainId, + currencyType: token.symbol, + currencyNumber: Double(amountTextField.text ?? "0")!, + receiveAddress: addressTextField.text ?? "", + outcomeAddress: WalletRealmTool.getCurrentAppModel().currentWallet!.address, + transactionType: .normal + ) + } + }*/ + + // MARK: - UI + func setupUI() { + let wallet = WalletRealmTool.getCurrentAppModel().currentWallet! + title = "\(token.symbol)转账" + walletIconView.image = UIImage(data: wallet.iconData) + walletNameLabel.text = wallet.name + walletAddressLabel.text = wallet.address + tokenBalanceButton.setTitle("\(token.tokenBalance)\(token.symbol)", for: .normal) + updateGasCost() + } + + private func updateGasCost() { + gasCostLabel.text = "\(paramBuilder.txFeeNatural.decimal)\(paramBuilder.nativeCoinSymbol)" + } +} + +extension SendTransactionViewController { + var isEffectiveTransferInfo: Bool { + guard Address.isValid(paramBuilder.to) && paramBuilder.to != "0x" else { + Toast.showToast(text: "您的地址错误,请重新输入") + return false + } + + // TODO: FIXME: erc20 requires eth balance as tx fee + if paramBuilder.hasSufficientBalance { + return true + } + + if paramBuilder.tokenBalance <= BigUInt(0) { + Toast.showToast(text: "请确保账户剩余\(token.gasSymbol)高于矿工费用,以便顺利完成转账~") + return false + } + let alert = UIAlertController(title: "您输入的金额超过您的余额,是否全部转出?", message: "", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "确认", style: .default, handler: { (_) in + self.transactionAvailableBalance() + })) + alert.addAction(UIAlertAction(title: "取消", style: .destructive, handler: { (_) in + self.amountTextField.text = "" + })) + present(alert, animated: true, completion: nil) + return false + } +} + +extension SendTransactionViewController: UITextFieldDelegate { + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + if textField == amountTextField { + let character: String + if (textField.text?.contains("."))! { + character = "0123456789" + } else { + character = "0123456789." + } + guard CharacterSet(charactersIn: character).isSuperset(of: CharacterSet(charactersIn: string)) else { + return false + } + return true + } + return true + } +} + +extension SendTransactionViewController: QRCodeViewControllerDelegate { + func didBackQRCodeMessage(codeResult: String) { + addressTextField.text = codeResult + } +} + +extension SendTransactionViewController { + override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { + return indexPath.row == 2 + } + + override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { + return indexPath.row == 2 ? indexPath : nil + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = super.tableView(tableView, cellForRowAt: indexPath) + if indexPath.section == 0 && indexPath.row == 2 { + cell.accessoryType = .disclosureIndicator + } + return cell + } +} From 7395368583d22578f8d7c4aae73801ba23e4fe99 Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 19 Nov 2018 11:50:01 +0900 Subject: [PATCH 100/315] Increase realm schemaVersion although current change breaks db sctructure and requires clean installation --- Neuron/Utils/RealmHelper.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neuron/Utils/RealmHelper.swift b/Neuron/Utils/RealmHelper.swift index 030b2bf5..453b21b3 100644 --- a/Neuron/Utils/RealmHelper.swift +++ b/Neuron/Utils/RealmHelper.swift @@ -15,7 +15,7 @@ class RealmHelper { realm.autorefresh = true return realm } - private static var schemaVersion: UInt64 = 3 + private static var schemaVersion: UInt64 = 4 static func configureRealm() { var config = Realm.Configuration() From 8d7ec8b62cde9ffb79dbdae3d782a78d5e6f63c6 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 19 Nov 2018 13:07:13 +0800 Subject: [PATCH 101/315] Modify task thread --- .../Common/TransactionStatusManager.swift | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/Neuron/Services/Common/TransactionStatusManager.swift b/Neuron/Services/Common/TransactionStatusManager.swift index 020ef463..e6707261 100644 --- a/Neuron/Services/Common/TransactionStatusManager.swift +++ b/Neuron/Services/Common/TransactionStatusManager.swift @@ -25,20 +25,11 @@ protocol TransactionStatusManagerDelegate: NSObjectProtocol { class TransactionStatusManager: NSObject { static let manager = TransactionStatusManager() - private var realm: Realm! private var transactions = [SentTransaction]() private override init() { delegates = NSHashTable(options: .weakMemory) super.init() - - createTaskThread() - perform { - self.realm = RealmHelper().realm - self.transactions = self.realm.objects(SentTransaction.self).filter({ (transaction) -> Bool in - return transaction.status == .pending - }) - } perform { self.checkSentTransactionStatus() } @@ -76,17 +67,13 @@ class TransactionStatusManager: NSObject { // MARK: - func getTransactions(walletAddress: String, tokenType: TokenModel.TokenType, tokenAddress: String) -> [TransactionDetails] { - var transactions: [TransactionDetails]? - syncPerform { - let ethereumNetwork = EthereumNetwork().host().absoluteString - transactions = self.realm.objects(SentTransaction.self).filter({ - $0.from == walletAddress && + let ethereumNetwork = EthereumNetwork().host().absoluteString + return self.realm.objects(SentTransaction.self).filter({ + $0.from == walletAddress && $0.tokenType == tokenType && $0.contractAddress == tokenAddress && ($0.ethereumNetwork == "" || $0.ethereumNetwork == ethereumNetwork) - }).map({ $0.transactionDetails() }) - } - return transactions ?? [] + }).map({ $0.transactionDetails() }) } // MARK: - Check transaction status @@ -95,6 +82,7 @@ class TransactionStatusManager: NSObject { @objc private func checkSentTransactionStatus() { NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(checkSentTransactionStatus), object: nil) guard self.transactions.count > 0 else { + deleteTaskThread() return } guard Thread.current == thread else { @@ -147,7 +135,8 @@ class TransactionStatusManager: NSObject { } // MARK: - Thread - private var thread: Thread! + private var thread: Thread? + private var runLoop: RunLoop? private typealias Block = () -> Void private class Task: NSObject { let block: Block @@ -157,45 +146,63 @@ class TransactionStatusManager: NSObject { } @objc private func perform(_ block: @escaping Block) { + let thread = getTaskThread() if Thread.current == thread { block() return } let task = Task(block: block) let sel = #selector(TransactionStatusManager.taskHandler(task:)) - self.perform(sel, on: self.thread, with: task, waitUntilDone: false) + self.perform(sel, on: thread, with: task, waitUntilDone: false) } @objc private func syncPerform(_ block: @escaping Block) { + let thread = getTaskThread() if Thread.current == thread { block() return } let task = Task(block: block) let sel = #selector(TransactionStatusManager.taskHandler(task:)) - self.perform(sel, on: self.thread, with: task, waitUntilDone: true) + self.perform(sel, on: thread, with: task, waitUntilDone: true) } @objc private func taskHandler(task: Task) { task.block() } - // func value(_ block: (TransactionStatusManager) -> T) -> T { - // let value = block(self) - // return value - // } - - private func createTaskThread() { + private func getTaskThread() -> Thread { + if let thread = thread { + return thread + } let group = DispatchGroup() let queue = DispatchQueue(label: "") group.enter() queue.async { self.thread = Thread.current + self.runLoop = RunLoop.current Thread.current.name = String(describing: TransactionStatusManager.self) RunLoop.current.add(NSMachPort(), forMode: .default) group.leave() - RunLoop.current.run() + CFRunLoopRun() } group.wait() + perform { + self.transactions = self.realm.objects(SentTransaction.self).filter({ (transaction) -> Bool in + return transaction.status == .pending + }) + } + return thread! + } + + private func deleteTaskThread() { + self.thread = nil + if let runloop = self.runLoop?.getCFRunLoop() { + CFRunLoopStop(runloop) + } + } + + private var realm: Realm! { + return RealmHelper().realm } } From 1e38a1a4ad6b616eba358c7fcafa08ee409490b9 Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 19 Nov 2018 17:43:48 +0900 Subject: [PATCH 102/315] Send transaction UI flow with BulletinBoard --- Neuron.xcodeproj/project.pbxproj | 46 +++- Neuron/AppDelegate.swift | 2 - .../Icons/success.imageset/Contents.json | 22 ++ .../Icons/success.imageset/success@2x.png | Bin 0 -> 5643 bytes .../Icons/success.imageset/success@3x.png | Bin 0 -> 8857 bytes Neuron/Constants/NibLoadable.swift | 3 +- Neuron/MainViewController.swift | 8 +- .../BaseNavigationController.swift | 0 .../Common/PageItems/PageItemAppearance.swift | 19 ++ .../Common/PageItems/PasswordPageItem.swift | 85 ++++++ .../PageItems/SendTransactionSummaryView.xib | 119 +++++++++ .../Common/PageItems/SuccessPageItem.swift | 24 ++ .../Common/PageItems/TxSummaryPageItem.swift | 46 ++++ .../Transaction/SendTransaction.storyboard | 243 +----------------- .../SendTransactionViewController.swift | 105 ++++---- .../Transaction/Transaction.storyboard | 14 +- .../TransactionHistoryViewController.swift | 4 +- .../TradeTableViewCell.swift | 0 .../TradeTableViewCell.xib | 0 .../TransactionTableviewCell.swift | 0 20 files changed, 423 insertions(+), 317 deletions(-) create mode 100644 Neuron/Assets.xcassets/Icons/success.imageset/Contents.json create mode 100644 Neuron/Assets.xcassets/Icons/success.imageset/success@2x.png create mode 100644 Neuron/Assets.xcassets/Icons/success.imageset/success@3x.png rename Neuron/Sections/{Base => Common}/BaseNavigationController.swift (100%) create mode 100644 Neuron/Sections/Common/PageItems/PageItemAppearance.swift create mode 100644 Neuron/Sections/Common/PageItems/PasswordPageItem.swift create mode 100644 Neuron/Sections/Common/PageItems/SendTransactionSummaryView.xib create mode 100644 Neuron/Sections/Common/PageItems/SuccessPageItem.swift create mode 100644 Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift rename Neuron/Sections/Transaction/{TableViewCell => Views}/TradeTableViewCell.swift (100%) rename Neuron/Sections/Transaction/{TableViewCell => Views}/TradeTableViewCell.xib (100%) rename Neuron/Sections/Transaction/{TableViewCell => Views}/TransactionTableviewCell.swift (100%) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 751f3ad0..9ca7edd8 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -21,6 +21,11 @@ 1A5515E521898A1000D34791 /* WalletKeystoreManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5515E421898A1000D34791 /* WalletKeystoreManager.swift */; }; 1A5515E7218D36B600D34791 /* WalletManager+Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A5515E6218D36B600D34791 /* WalletManager+Errors.swift */; }; 1A5515EA218D3F0300D34791 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1A5515EC218D3F0300D34791 /* Localizable.strings */; }; + 1A60079721A274D000C7B712 /* SendTransactionSummaryView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1A60079621A274D000C7B712 /* SendTransactionSummaryView.xib */; }; + 1A60079C21A28B8C00C7B712 /* TxSummaryPageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A60079B21A28B8C00C7B712 /* TxSummaryPageItem.swift */; }; + 1A60079E21A28C1400C7B712 /* PasswordPageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A60079D21A28C1400C7B712 /* PasswordPageItem.swift */; }; + 1A6007A021A29C2C00C7B712 /* SuccessPageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A60079F21A29C2C00C7B712 /* SuccessPageItem.swift */; }; + 1A6007A221A2AA9A00C7B712 /* PageItemAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A6007A121A2AA9A00C7B712 /* PageItemAppearance.swift */; }; 1A60B6FC2186DB1C00FEFB3A /* Web3Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A60B6FB2186DB1C00FEFB3A /* Web3Utils.swift */; }; 1A69465C21916BE5000093A2 /* EthereumTxSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A69465B21916BE5000093A2 /* EthereumTxSender.swift */; }; 1A69465E21916C3D000093A2 /* AppChainTxSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A69465D21916C3D000093A2 /* AppChainTxSender.swift */; }; @@ -220,6 +225,11 @@ 1A5515E6218D36B600D34791 /* WalletManager+Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WalletManager+Errors.swift"; sourceTree = ""; }; 1A5515EB218D3F0300D34791 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 1A5515ED218D3F4500D34791 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; + 1A60079621A274D000C7B712 /* SendTransactionSummaryView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SendTransactionSummaryView.xib; sourceTree = ""; }; + 1A60079B21A28B8C00C7B712 /* TxSummaryPageItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TxSummaryPageItem.swift; sourceTree = ""; }; + 1A60079D21A28C1400C7B712 /* PasswordPageItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordPageItem.swift; sourceTree = ""; }; + 1A60079F21A29C2C00C7B712 /* SuccessPageItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuccessPageItem.swift; sourceTree = ""; }; + 1A6007A121A2AA9A00C7B712 /* PageItemAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageItemAppearance.swift; sourceTree = ""; }; 1A60B6FB2186DB1C00FEFB3A /* Web3Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Web3Utils.swift; sourceTree = ""; }; 1A69465B21916BE5000093A2 /* EthereumTxSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumTxSender.swift; sourceTree = ""; }; 1A69465D21916C3D000093A2 /* AppChainTxSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppChainTxSender.swift; sourceTree = ""; }; @@ -464,6 +474,18 @@ path = Extensions; sourceTree = ""; }; + 1A60079A21A28B5600C7B712 /* PageItems */ = { + isa = PBXGroup; + children = ( + 1A60079621A274D000C7B712 /* SendTransactionSummaryView.xib */, + 1A60079B21A28B8C00C7B712 /* TxSummaryPageItem.swift */, + 1A60079D21A28C1400C7B712 /* PasswordPageItem.swift */, + 1A60079F21A29C2C00C7B712 /* SuccessPageItem.swift */, + 1A6007A121A2AA9A00C7B712 /* PageItemAppearance.swift */, + ); + path = PageItems; + sourceTree = ""; + }; 1A694655219044FE000093A2 /* Ethereum */ = { isa = PBXGroup; children = ( @@ -716,7 +738,6 @@ 8928C22720AE7CB500C3103E /* Dapp */, 8928C27320B29ABB00C3103E /* Wallet */, 8922685820DE4EA50021A85F /* Settings */, - 8928C22820AE7CB500C3103E /* Base */, 8928C22A20AE7CB500C3103E /* Common */, 898A1A1D20B7B4F300ECB465 /* Transaction */, 6E9D7CBB216B37E90044176D /* Authentication */, @@ -734,17 +755,11 @@ path = Dapp; sourceTree = ""; }; - 8928C22820AE7CB500C3103E /* Base */ = { - isa = PBXGroup; - children = ( - 8928C24420AE90C000C3103E /* BaseNavigationController.swift */, - ); - path = Base; - sourceTree = ""; - }; 8928C22A20AE7CB500C3103E /* Common */ = { isa = PBXGroup; children = ( + 1A60079A21A28B5600C7B712 /* PageItems */, + 8928C24420AE90C000C3103E /* BaseNavigationController.swift */, 89812129218D3161000813C8 /* QRCodeViewController.swift */, 8964A17F20BE94F70086848F /* NEPickerView.swift */, 1A207DD42137D8CF008DC306 /* TabbedButtonsView.swift */, @@ -954,26 +969,26 @@ 1A75EBFC21A1948F008D484B /* SendTransaction.storyboard */, 1A75EBFE21A194B1008D484B /* SendTransactionViewController.swift */, 89F79670213BD8C40064808A /* Transaction.storyboard */, + 89C6145F2141421400DC3DF4 /* TransactionHistoryViewController.swift */, 6E8168F6218874B1007641BA /* TransactionViewController.swift */, 6E8168F821887A31007641BA /* TransactionGasPriceViewController.swift */, 6E4F961A2189A82900372D3E /* TransactionConfirmViewController.swift */, - 89C6145F2141421400DC3DF4 /* TransactionHistoryViewController.swift */, 6E4F9617218973F200372D3E /* TransactionParamBuilder.swift */, 898A1A2820B7F0FC00ECB465 /* TradeDetailsController.swift */, 898A1A2920B7F0FC00ECB465 /* TradeDetailsController.xib */, - 898A1A2C20B8033C00ECB465 /* TableViewCell */, + 898A1A2C20B8033C00ECB465 /* Views */, ); path = Transaction; sourceTree = ""; }; - 898A1A2C20B8033C00ECB465 /* TableViewCell */ = { + 898A1A2C20B8033C00ECB465 /* Views */ = { isa = PBXGroup; children = ( 898A1A2420B7DAA300ECB465 /* TransactionTableviewCell.swift */, 898A1A2D20B8044900ECB465 /* TradeTableViewCell.swift */, 898A1A2E20B8044900ECB465 /* TradeTableViewCell.xib */, ); - path = TableViewCell; + path = Views; sourceTree = ""; }; 898CA98420EA21E90059ECA3 /* Common */ = { @@ -1139,6 +1154,7 @@ 891B663D213EAA1900B0FCB0 /* WalletManagement.storyboard in Resources */, 896D043720AE69C5002CFF6A /* LaunchScreen.storyboard in Resources */, 898A1A1020B6539800ECB465 /* AddAssetTableViewCell.xib in Resources */, + 1A60079721A274D000C7B712 /* SendTransactionSummaryView.xib in Resources */, 1A207DD12137B412008DC306 /* RequestPayment.storyboard in Resources */, 6E859CB82176DC3100637E1C /* Overlay.storyboard in Resources */, 898CA98A20EA2E770059ECA3 /* tokens-eth.json in Resources */, @@ -1338,6 +1354,7 @@ 893532F4217DECF400404C0B /* MessageSignShowViewController.swift in Sources */, 6E83CB90216F573100029324 /* GuideCollectionViewCell.swift in Sources */, 1AEBF3E4219BA1E700653FF5 /* BigUInt+Extension.swift in Sources */, + 1A6007A221A2AA9A00C7B712 /* PageItemAppearance.swift in Sources */, 89B895B120F8B0CD00B9468B /* BrowserViewController.swift in Sources */, 1A207DCF2137B3F7008DC306 /* RequestPaymentViewController.swift in Sources */, 89D84CE72147C671006B0287 /* TraitsCollectionViewCell.swift in Sources */, @@ -1395,6 +1412,7 @@ 89F9E73E20DEBDE8009E68D4 /* ExportKeystoreController.swift in Sources */, 89D935252139363A002FAEEC /* TokenTableViewCell.swift in Sources */, 6E9F9D5A219AAF99009ED8B2 /* UINavigationItem+FixSpace.swift in Sources */, + 1A60079E21A28C1400C7B712 /* PasswordPageItem.swift in Sources */, 89C614602141421400DC3DF4 /* TransactionHistoryViewController.swift in Sources */, 8913437820CA56CA00A17AEF /* RealmHelper.swift in Sources */, 8955580520DCA7F700FC92D8 /* PasswordValidator.swift in Sources */, @@ -1414,6 +1432,7 @@ 896D042D20AE69C1002CFF6A /* AppDelegate.swift in Sources */, 6E867E03216C3EC800BD6FE5 /* AuthSelectWalletViewController.swift in Sources */, 89BEBE6B20EF82B6007D2705 /* EthereumNetwork.swift in Sources */, + 1A60079C21A28B8C00C7B712 /* TxSummaryPageItem.swift in Sources */, 89BEBE7220F12647007D2705 /* CommonWebViewController.swift in Sources */, 6E867E05216C3F1500BD6FE5 /* AuthPasswordViewController.swift in Sources */, 8964A18720BFE6860086848F /* ImportWalletController.swift in Sources */, @@ -1431,6 +1450,7 @@ 89AB35CA213D113E00A2BF48 /* WalletTableViewCell.swift in Sources */, 8928C24920AE91E600C3103E /* DappViewController.swift in Sources */, 898CA98820EA23940059ECA3 /* EthNativeTokenService.swift in Sources */, + 1A6007A021A29C2C00C7B712 /* SuccessPageItem.swift in Sources */, 89C6E09920E07F8E00C94026 /* ShareItem.swift in Sources */, 6E867E09216C862900BD6FE5 /* NoScreenshot.swift in Sources */, 89C614592140FE6600DC3DF4 /* CurrencyTableViewCell.swift in Sources */, diff --git a/Neuron/AppDelegate.swift b/Neuron/AppDelegate.swift index 6665289c..b8e858b4 100644 --- a/Neuron/AppDelegate.swift +++ b/Neuron/AppDelegate.swift @@ -27,8 +27,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { GuideService.shared.register() AuthenticationService.shared.register() SensorsAnalytics.configureSensors() - UINavigationBar.fixSpace - UINavigationItem.fixSpace return true } diff --git a/Neuron/Assets.xcassets/Icons/success.imageset/Contents.json b/Neuron/Assets.xcassets/Icons/success.imageset/Contents.json new file mode 100644 index 00000000..fa04bc9e --- /dev/null +++ b/Neuron/Assets.xcassets/Icons/success.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "success@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "success@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Icons/success.imageset/success@2x.png b/Neuron/Assets.xcassets/Icons/success.imageset/success@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..faca197f96614ad4787e5f22cd79248b47f67d61 GIT binary patch literal 5643 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 literal 0 HcmV?d00001 diff --git a/Neuron/Assets.xcassets/Icons/success.imageset/success@3x.png b/Neuron/Assets.xcassets/Icons/success.imageset/success@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e956b9e624c7e909cdcc0a3b0f7ff733d568c5c9 GIT binary patch literal 8857 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 UdLRw#TN8k~vW`;KbK8jj0mg}#$N&HU literal 0 HcmV?d00001 diff --git a/Neuron/Constants/NibLoadable.swift b/Neuron/Constants/NibLoadable.swift index 2ae5524b..a7afeaea 100644 --- a/Neuron/Constants/NibLoadable.swift +++ b/Neuron/Constants/NibLoadable.swift @@ -9,12 +9,11 @@ import UIKit protocol NibLoadable { - } extension NibLoadable where Self: UIView { static func loadFromNib(_ nibname: String? = nil) -> Self { - let loadName = nibname == nil ? "\(self)" : nibname! + let loadName = nibname ?? "\(self)" return Bundle.main.loadNibNamed(loadName, owner: nil, options: nil)?.first as! Self } } diff --git a/Neuron/MainViewController.swift b/Neuron/MainViewController.swift index 214b92cf..60dce89c 100644 --- a/Neuron/MainViewController.swift +++ b/Neuron/MainViewController.swift @@ -9,13 +9,12 @@ import UIKit class MainViewController: UITabBarController, UITabBarControllerDelegate { - override func viewDidLoad() { super.viewDidLoad() delegate = self - applyBarStyle() + applyStyle() determineWalletViewController() NotificationCenter.default.addObserver(self, selector: #selector(determineWalletViewController), name: .allWalletsDeleted, object: nil) @@ -45,7 +44,7 @@ class MainViewController: UITabBarController, UITabBarControllerDelegate { } } - private func applyBarStyle() { + private func applyStyle() { UITabBarItem.appearance().titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -4) UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: AppColor.newThemeColor], for: .selected) @@ -55,6 +54,9 @@ class MainViewController: UITabBarController, UITabBarControllerDelegate { UIBarButtonItem.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], for: .normal) UIBarButtonItem.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], for: .highlighted) + + UINavigationBar.fixSpace + UINavigationItem.fixSpace } @objc diff --git a/Neuron/Sections/Base/BaseNavigationController.swift b/Neuron/Sections/Common/BaseNavigationController.swift similarity index 100% rename from Neuron/Sections/Base/BaseNavigationController.swift rename to Neuron/Sections/Common/BaseNavigationController.swift diff --git a/Neuron/Sections/Common/PageItems/PageItemAppearance.swift b/Neuron/Sections/Common/PageItems/PageItemAppearance.swift new file mode 100644 index 00000000..95b62dc2 --- /dev/null +++ b/Neuron/Sections/Common/PageItems/PageItemAppearance.swift @@ -0,0 +1,19 @@ +// +// PageItemAppearance.swift +// Neuron +// +// Created by James Chen on 2018/11/19. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import BLTNBoard + +struct PageItemAppearance { + static var `default`: BLTNItemAppearance = { + let appearance = BLTNItemAppearance() + appearance.titleFontSize = 18 + appearance.titleTextColor = ColorFromString(hex: "2E313E") + return appearance + }() +} diff --git a/Neuron/Sections/Common/PageItems/PasswordPageItem.swift b/Neuron/Sections/Common/PageItems/PasswordPageItem.swift new file mode 100644 index 00000000..d73a767b --- /dev/null +++ b/Neuron/Sections/Common/PageItems/PasswordPageItem.swift @@ -0,0 +1,85 @@ +// +// PasswordPageItem.swift +// Neuron +// +// Created by James Chen on 2018/11/19. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import BLTNBoard + +class PasswordPageItem: BLTNPageItem { + var passwordField: UITextField! + + var errorMessage: String? { + didSet { + if let message = errorMessage { + descriptionLabel!.textColor = .red + descriptionLabel!.text = message + } else { + // How to revert to default style? + } + } + } + + static func create() -> PasswordPageItem { + let item = PasswordPageItem(title: "请输入钱包密码") + item.appearance = PageItemAppearance.default + item.descriptionText = "" + item.actionButtonTitle = "确认发送" + return item + } + + override func makeViewsUnderDescription(with interfaceBuilder: BLTNInterfaceBuilder) -> [UIView]? { + passwordField = interfaceBuilder.makeTextField(placeholder: "请输入钱包密码", returnKey: .done, delegate: self) + passwordField.isSecureTextEntry = true + return [passwordField] + } + + override func tearDown() { + super.tearDown() + passwordField?.delegate = nil + } + + override func actionButtonTapped(sender: UIButton) { + passwordField.resignFirstResponder() + + if validateInput() { + super.actionButtonTapped(sender: sender) + } + } + + private func isInputValid(text: String?) -> Bool { + if text == nil || text!.isEmpty { + return false + } + + return true + } + + @discardableResult + private func validateInput() -> Bool { + if isInputValid(text: passwordField.text) { + return true + } else { + errorMessage = "请输入密码" + return false + } + } +} + +extension PasswordPageItem: UITextFieldDelegate { + func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { + return true + } + + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.resignFirstResponder() + return true + } + + func textFieldDidEndEditing(_ textField: UITextField) { + validateInput() + } +} diff --git a/Neuron/Sections/Common/PageItems/SendTransactionSummaryView.xib b/Neuron/Sections/Common/PageItems/SendTransactionSummaryView.xib new file mode 100644 index 00000000..886b7057 --- /dev/null +++ b/Neuron/Sections/Common/PageItems/SendTransactionSummaryView.xib @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Neuron/Sections/Common/PageItems/SuccessPageItem.swift b/Neuron/Sections/Common/PageItems/SuccessPageItem.swift new file mode 100644 index 00000000..a999e191 --- /dev/null +++ b/Neuron/Sections/Common/PageItems/SuccessPageItem.swift @@ -0,0 +1,24 @@ +// +// SuccessPageItem.swift +// Neuron +// +// Created by James Chen on 2018/11/19. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import BLTNBoard + +class SuccessPageItem: BLTNPageItem { + static func create(title: String = "操作成功") -> SuccessPageItem { + let item = SuccessPageItem(title: title) + + item.appearance = PageItemAppearance.default + item.image = UIImage(named: "success") + item.actionButtonTitle = "确定" + item.isDismissable = false + + return item + + } +} diff --git a/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift b/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift new file mode 100644 index 00000000..f348c890 --- /dev/null +++ b/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift @@ -0,0 +1,46 @@ +// +// TxSummaryPageItem.swift +// Neuron +// +// Created by James Chen on 2018/11/19. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import BLTNBoard + +// Showing transaction summary before sending out. +class TxSummaryPageItem: BLTNPageItem { + private let summaryView = SendTransactionSummaryView.loadFromNib() + + static func create() -> TxSummaryPageItem { + let item = TxSummaryPageItem(title: "转账信息") + item.appearance = PageItemAppearance.default + item.actionButtonTitle = "确认转账" + return item + } + + func update(_ param: TransactionParamBuilder) { + summaryView.amountLabel.text = Double.fromAmount(param.value, decimals: param.decimals).decimal + " " + let range = NSRange(location: summaryView.amountLabel.text!.lengthOfBytes(using: .utf8), length: param.symbol.lengthOfBytes(using: .utf8)) + summaryView.amountLabel.text! += param.symbol + let attributedText = NSMutableAttributedString(attributedString: summaryView.amountLabel.attributedText!) + attributedText.addAttributes([NSAttributedString.Key.font: UIFont.systemFont(ofSize: 24)], range: range) + summaryView.amountLabel.attributedText = attributedText + + summaryView.fromLabel.text = param.from + summaryView.toLabel.text = param.to + summaryView.txFeeLabel.text = "\(param.txFeeNatural.decimal) \(param.nativeCoinSymbol)" + } + + override func makeViewsUnderTitle(with interfaceBuilder: BLTNInterfaceBuilder) -> [UIView]? { + return [summaryView] + } +} + +class SendTransactionSummaryView: UIView, NibLoadable { + @IBOutlet weak var amountLabel: UILabel! + @IBOutlet weak var fromLabel: UILabel! + @IBOutlet weak var toLabel: UILabel! + @IBOutlet weak var txFeeLabel: UILabel! +} diff --git a/Neuron/Sections/Transaction/SendTransaction.storyboard b/Neuron/Sections/Transaction/SendTransaction.storyboard index 876efa0e..92ef1c79 100644 --- a/Neuron/Sections/Transaction/SendTransaction.storyboard +++ b/Neuron/Sections/Transaction/SendTransaction.storyboard @@ -10,232 +10,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -301,7 +75,7 @@ - + @@ -485,7 +259,7 @@ - + @@ -587,7 +361,7 @@ - + @@ -646,7 +420,7 @@ - + @@ -746,21 +520,12 @@ - - - - - - - - - diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index 114e9b23..df953b68 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -7,22 +7,43 @@ // import UIKit +import BLTNBoard import BigInt import AppChain import Web3swift import EthereumAddress class SendTransactionViewController: UITableViewController { - @IBOutlet weak var walletIconView: UIImageView! - @IBOutlet weak var walletNameLabel: UILabel! - @IBOutlet weak var walletAddressLabel: UILabel! - @IBOutlet weak var tokenBalanceButton: UIButton! - @IBOutlet weak var amountTextField: UITextField! - @IBOutlet weak var gasCostLabel: UILabel! - @IBOutlet weak var addressTextField: UITextField! + @IBOutlet private weak var walletIconView: UIImageView! + @IBOutlet private weak var walletNameLabel: UILabel! + @IBOutlet private weak var walletAddressLabel: UILabel! + @IBOutlet private weak var tokenBalanceButton: UIButton! + @IBOutlet private weak var amountTextField: UITextField! + @IBOutlet private weak var gasCostLabel: UILabel! + @IBOutlet private weak var addressTextField: UITextField! private var paramBuilder: TransactionParamBuilder! private var observers = [NSKeyValueObservation]() + + private lazy var summaryPageItem: TxSummaryPageItem = { + return TxSummaryPageItem.create() + }() + private lazy var bulletinManager: BLTNItemManager = { + let passwordPageItem = PasswordPageItem.create() + + summaryPageItem.next = passwordPageItem + summaryPageItem.actionHandler = { item in + item.manager?.displayNextItem() + } + + passwordPageItem.actionHandler = { [weak self] item in + item.manager?.displayActivityIndicator() + self?.sendTransaction(password: passwordPageItem.passwordField.text!) + } + + return BLTNItemManager(rootItem: summaryPageItem) + }() + var token: TokenModel! var confirmViewController: TransactionConfirmViewController? @@ -49,14 +70,16 @@ class SendTransactionViewController: UITableViewController { } } - // MARK: - Event @IBAction func next(_ sender: Any) { + view.endEditing(true) + let amount = Double(amountTextField.text!) ?? 0.0 paramBuilder.value = amount.toAmount(token.decimals) paramBuilder.to = addressTextField.text! if isEffectiveTransferInfo { - performSegue(withIdentifier: "TransactionConfirmViewController", sender: nil) + summaryPageItem.update(paramBuilder) + bulletinManager.showBulletin(above: self) } } @@ -78,28 +101,6 @@ class SendTransactionViewController: UITableViewController { } } - // TODO: tx sent - /* - func transactionCompletion(_ transactionService: TransactionParamBuilder, result: TransactionParamBuilder.Result) { - switch result { - case .error(let error): - Toast.showToast(text: error.localizedDescription) - default: - Toast.showToast(text: "转账成功,请稍后刷新查看") - confirmViewController?.dismiss() - navigationController?.popViewController(animated: true) - - SensorsAnalytics.Track.transaction( - chainType: token.chainId, - currencyType: token.symbol, - currencyNumber: Double(amountTextField.text ?? "0")!, - receiveAddress: addressTextField.text ?? "", - outcomeAddress: WalletRealmTool.getCurrentAppModel().currentWallet!.address, - transactionType: .normal - ) - } - }*/ - // MARK: - UI func setupUI() { let wallet = WalletRealmTool.getCurrentAppModel().currentWallet! @@ -114,6 +115,20 @@ class SendTransactionViewController: UITableViewController { private func updateGasCost() { gasCostLabel.text = "\(paramBuilder.txFeeNatural.decimal)\(paramBuilder.nativeCoinSymbol)" } + + private func sendTransaction(password: String) { + DispatchQueue.global().async { + // TODO: send tx for real + Thread.sleep(forTimeInterval: 3) + DispatchQueue.main.async { + let successPageItem = SuccessPageItem.create(title: "交易已发送") + successPageItem.actionHandler = { item in + item.manager?.dismissBulletin(animated: true) + } + self.bulletinManager.push(item: successPageItem) + } + } + } } extension SendTransactionViewController { @@ -144,6 +159,12 @@ extension SendTransactionViewController { } } +extension SendTransactionViewController { + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + } +} + extension SendTransactionViewController: UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if textField == amountTextField { @@ -167,25 +188,3 @@ extension SendTransactionViewController: QRCodeViewControllerDelegate { addressTextField.text = codeResult } } - -extension SendTransactionViewController { - override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { - return indexPath.row == 2 - } - - override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { - return indexPath.row == 2 ? indexPath : nil - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = super.tableView(tableView, cellForRowAt: indexPath) - if indexPath.section == 0 && indexPath.row == 2 { - cell.accessoryType = .disclosureIndicator - } - return cell - } -} diff --git a/Neuron/Sections/Transaction/Transaction.storyboard b/Neuron/Sections/Transaction/Transaction.storyboard index 79e75a37..86e5fcee 100644 --- a/Neuron/Sections/Transaction/Transaction.storyboard +++ b/Neuron/Sections/Transaction/Transaction.storyboard @@ -960,7 +960,7 @@ - + @@ -1015,11 +1015,19 @@ - + + + + + + + + + - + diff --git a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift index dc89d081..5865f3d5 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift @@ -59,8 +59,8 @@ class TransactionHistoryViewController: UIViewController, UITableViewDelegate, U let requestPaymentViewController = segue.destination as! RequestPaymentViewController let appModel = WalletRealmTool.getCurrentAppModel() requestPaymentViewController.appModel = appModel - } else if segue.identifier == "transaction" { - let controller = segue.destination as! TransactionViewController + } else if segue.identifier == "sendTransaction" { + let controller = segue.destination as! SendTransactionViewController controller.token = service?.token } } diff --git a/Neuron/Sections/Transaction/TableViewCell/TradeTableViewCell.swift b/Neuron/Sections/Transaction/Views/TradeTableViewCell.swift similarity index 100% rename from Neuron/Sections/Transaction/TableViewCell/TradeTableViewCell.swift rename to Neuron/Sections/Transaction/Views/TradeTableViewCell.swift diff --git a/Neuron/Sections/Transaction/TableViewCell/TradeTableViewCell.xib b/Neuron/Sections/Transaction/Views/TradeTableViewCell.xib similarity index 100% rename from Neuron/Sections/Transaction/TableViewCell/TradeTableViewCell.xib rename to Neuron/Sections/Transaction/Views/TradeTableViewCell.xib diff --git a/Neuron/Sections/Transaction/TableViewCell/TransactionTableviewCell.swift b/Neuron/Sections/Transaction/Views/TransactionTableviewCell.swift similarity index 100% rename from Neuron/Sections/Transaction/TableViewCell/TransactionTableviewCell.swift rename to Neuron/Sections/Transaction/Views/TransactionTableviewCell.swift From e638dc5f130d90f3b4fb471b3c3a19d5a2d87916 Mon Sep 17 00:00:00 2001 From: LuFP Date: Mon, 19 Nov 2018 17:51:45 +0800 Subject: [PATCH 103/315] Main ui feature --- .../Contents.json | 4 +- .../TabBar/Main_off.imageset/Main_off@2x.png | Bin 0 -> 1688 bytes .../TabBar/Main_off.imageset/Main_off@3x.png | Bin 0 -> 2545 bytes .../Contents.json | 4 +- .../TabBar/Main_on.imageset/Main_on@2x.png | Bin 0 -> 972 bytes .../TabBar/Main_on.imageset/Main_on@3x.png | Bin 0 -> 1429 bytes .../setting_off.imageset/setting_off@2x.png | Bin 2454 -> 0 bytes .../setting_off.imageset/setting_off@3x.png | Bin 3866 -> 0 bytes .../setting_on.imageset/setting_on@2x.png | Bin 2008 -> 0 bytes .../setting_on.imageset/setting_on@3x.png | Bin 3679 -> 0 bytes Neuron/Sections/Settings/Settings.storyboard | 414 +++++++++++------- .../Settings/SettingsViewController.swift | 79 ++-- .../Wallet/SelectWalletController.swift | 2 +- .../Sections/Wallet/SwitchWallet.storyboard | 36 +- Neuron/Sections/Wallet/Wallet.storyboard | 20 +- .../Wallet/WalletViewController.swift | 3 +- 16 files changed, 303 insertions(+), 259 deletions(-) rename Neuron/Assets.xcassets/TabBar/{setting_on.imageset => Main_off.imageset}/Contents.json (77%) create mode 100644 Neuron/Assets.xcassets/TabBar/Main_off.imageset/Main_off@2x.png create mode 100644 Neuron/Assets.xcassets/TabBar/Main_off.imageset/Main_off@3x.png rename Neuron/Assets.xcassets/TabBar/{setting_off.imageset => Main_on.imageset}/Contents.json (76%) create mode 100644 Neuron/Assets.xcassets/TabBar/Main_on.imageset/Main_on@2x.png create mode 100644 Neuron/Assets.xcassets/TabBar/Main_on.imageset/Main_on@3x.png delete mode 100644 Neuron/Assets.xcassets/TabBar/setting_off.imageset/setting_off@2x.png delete mode 100644 Neuron/Assets.xcassets/TabBar/setting_off.imageset/setting_off@3x.png delete mode 100644 Neuron/Assets.xcassets/TabBar/setting_on.imageset/setting_on@2x.png delete mode 100644 Neuron/Assets.xcassets/TabBar/setting_on.imageset/setting_on@3x.png diff --git a/Neuron/Assets.xcassets/TabBar/setting_on.imageset/Contents.json b/Neuron/Assets.xcassets/TabBar/Main_off.imageset/Contents.json similarity index 77% rename from Neuron/Assets.xcassets/TabBar/setting_on.imageset/Contents.json rename to Neuron/Assets.xcassets/TabBar/Main_off.imageset/Contents.json index 6459ab6c..1b2d39cf 100644 --- a/Neuron/Assets.xcassets/TabBar/setting_on.imageset/Contents.json +++ b/Neuron/Assets.xcassets/TabBar/Main_off.imageset/Contents.json @@ -6,12 +6,12 @@ }, { "idiom" : "universal", - "filename" : "setting_on@2x.png", + "filename" : "Main_off@2x.png", "scale" : "2x" }, { "idiom" : "universal", - "filename" : "setting_on@3x.png", + "filename" : "Main_off@3x.png", "scale" : "3x" } ], diff --git a/Neuron/Assets.xcassets/TabBar/Main_off.imageset/Main_off@2x.png b/Neuron/Assets.xcassets/TabBar/Main_off.imageset/Main_off@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..60e5a4eb22b799b634ebf18eac53e104013254e5 GIT binary patch literal 1688 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 literal 0 HcmV?d00001 diff --git a/Neuron/Assets.xcassets/TabBar/setting_off.imageset/setting_off@2x.png b/Neuron/Assets.xcassets/TabBar/setting_off.imageset/setting_off@2x.png deleted file mode 100644 index 21d17bb6cba8a3802d74e9c7014e76b927a3b6c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2454 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 U6_G>ZhX4Qo07*qoM6N<$f(?qfR{#J2 diff --git a/Neuron/Assets.xcassets/TabBar/setting_off.imageset/setting_off@3x.png b/Neuron/Assets.xcassets/TabBar/setting_off.imageset/setting_off@3x.png deleted file mode 100644 index 20e4b4399ada49272bf7e982e98dabd6f6f09e62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3866 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 diff --git a/Neuron/Assets.xcassets/TabBar/setting_on.imageset/setting_on@2x.png b/Neuron/Assets.xcassets/TabBar/setting_on.imageset/setting_on@2x.png deleted file mode 100644 index af8d6bd6715cc246f0a2a8dc02dc40cbf0880bf6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2008 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 - + - + - - - + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + - - - - - - + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + @@ -207,6 +285,16 @@ + + + + + + + + + + @@ -592,11 +680,11 @@ - + - + @@ -616,20 +704,20 @@ + + - + - - diff --git a/Neuron/Sections/Settings/SettingsViewController.swift b/Neuron/Sections/Settings/SettingsViewController.swift index 8070d55f..5a948cb2 100644 --- a/Neuron/Sections/Settings/SettingsViewController.swift +++ b/Neuron/Sections/Settings/SettingsViewController.swift @@ -10,25 +10,30 @@ import UIKit import SafariServices class SettingsViewController: UITableViewController { - var rowIdentifiers = [ - String(describing: SettingCurrencyTableViewCell.self), - "SettingSwitchEthereumNetwork", - String(describing: SettingAuthenticationTableViewCell.self), - "SettingAboutUsTableViewCell", - "SettingForumsTableViewCell", - "SettingContactCustomerServiceTableViewCell" - ] - + @IBOutlet weak var iconImageView: UIImageView! + @IBOutlet weak var nameLabel: UILabel! + @IBOutlet weak var addressLabel: UILabel! + @IBOutlet weak var currencyLabel: UILabel! + @IBOutlet weak var ethereumNetworkLabel: UILabel! + @IBOutlet var authenticationSwitch: UISwitch! + override func viewDidLoad() { super.viewDidLoad() - if !AuthenticationService.shared.isValid { - rowIdentifiers.remove(at: 2) - } + } + + func getDataForUI() { + let walletModel = WalletRealmTool.getCurrentAppModel().currentWallet! + nameLabel.text = walletModel.name + addressLabel.text = walletModel.address + iconImageView.image = UIImage(data: walletModel.iconData) + currencyLabel.text = LocalCurrencyService.shared.getLocalCurrencySelect().short + ethereumNetworkLabel.text = EthereumNetwork().currentNetwork.rawValue.capitalized + authenticationSwitch.isOn = AuthenticationService.shared.isEnable } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - tableView.reloadData() + getDataForUI() } @IBAction func authenticationSwitchChanged(_ sender: UISwitch) { @@ -37,45 +42,19 @@ class SettingsViewController: UITableViewController { } } - // MARK: UITableViewDataSource - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return rowIdentifiers.count - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: rowIdentifiers[indexPath.row])! - if let cell = cell as? SettingCurrencyTableViewCell { - if cell.reuseIdentifier == "SettingCurrencyTableViewCell" { - cell.localCurrencyLabel.text = LocalCurrencyService.shared.getLocalCurrencySelect().short - } else if cell.reuseIdentifier == "SettingSwitchEthereumNetwork" { - cell.localCurrencyLabel.text = EthereumNetwork().currentNetwork.rawValue.capitalized - } - } else if let cell = cell as? SettingAuthenticationTableViewCell { - cell.authenticationSwitch.isOn = AuthenticationService.shared.isEnable - cell.authenticationSwitch.addTarget(self, action: #selector(authenticationSwitchChanged), for: .touchUpInside) - } - return cell - } - - // MARK: UITableViewDelegate override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - guard let cell = tableView.cellForRow(at: indexPath) else { return } - if cell.reuseIdentifier == "SettingCurrencyTableViewCell" { - let controller: CurrencyViewController = UIStoryboard(name: .settings).instantiateViewController() - navigationController?.pushViewController(controller, animated: true) - } else if cell.reuseIdentifier == "SettingSwitchEthereumNetwork" { - let controller = storyboard!.instantiateViewController(withIdentifier: "switchNetworkViewController") as! SwitchNetworkViewController - navigationController?.pushViewController(controller, animated: true) - } else if cell.reuseIdentifier == "SettingAboutUsTableViewCell" { - let controller: AboutUsTableViewController = UIStoryboard(name: .settings).instantiateViewController() - navigationController?.pushViewController(controller, animated: true) - } else if cell.reuseIdentifier == "SettingForumsTableViewCell" { - let safariController = SFSafariViewController(url: URL(string: "https://forums.nervos.org/")!) - self.present(safariController, animated: true, completion: nil) - } else if cell.reuseIdentifier == "SettingContactCustomerServiceTableViewCell" { - UIPasteboard.general.string = "Nervos-Neuron" - Toast.showToast(text: "客服微信已复制") + if indexPath.section == 2 { + switch indexPath.row { + case 1: + UIPasteboard.general.string = "Nervos-Neuron" + Toast.showToast(text: "客服微信已复制") + case 2: + let safariController = SFSafariViewController(url: URL(string: "https://forums.nervos.org/")!) + self.present(safariController, animated: true, completion: nil) + default: + break + } } } } diff --git a/Neuron/Sections/Wallet/SelectWalletController.swift b/Neuron/Sections/Wallet/SelectWalletController.swift index fb3f9958..878553d7 100644 --- a/Neuron/Sections/Wallet/SelectWalletController.swift +++ b/Neuron/Sections/Wallet/SelectWalletController.swift @@ -63,6 +63,6 @@ class SelectWalletController: UITableViewController { appModel.currentWallet = walletModel } delegate?.selectWalletController(self, didSelectWallet: walletModel) - dismiss(animated: true, completion: nil) + navigationController?.popViewController(animated: true) } } diff --git a/Neuron/Sections/Wallet/SwitchWallet.storyboard b/Neuron/Sections/Wallet/SwitchWallet.storyboard index e3ca680d..cd0cfb08 100644 --- a/Neuron/Sections/Wallet/SwitchWallet.storyboard +++ b/Neuron/Sections/Wallet/SwitchWallet.storyboard @@ -1,15 +1,15 @@ - + - + - + @@ -17,12 +17,12 @@ - + - - - - - - - - - - + - @@ -198,6 +175,5 @@ - diff --git a/Neuron/Sections/Wallet/Wallet.storyboard b/Neuron/Sections/Wallet/Wallet.storyboard index 23dd3414..e22b038e 100644 --- a/Neuron/Sections/Wallet/Wallet.storyboard +++ b/Neuron/Sections/Wallet/Wallet.storyboard @@ -1,11 +1,11 @@ - + - + @@ -206,7 +206,7 @@ - + @@ -231,7 +231,7 @@ - + @@ -332,7 +332,9 @@ - + + + @@ -354,7 +356,7 @@ - + @@ -433,7 +435,7 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - @@ -259,7 +192,7 @@ - + diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index df953b68..95fc8a92 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -29,23 +29,16 @@ class SendTransactionViewController: UITableViewController { return TxSummaryPageItem.create() }() private lazy var bulletinManager: BLTNItemManager = { - let passwordPageItem = PasswordPageItem.create() - + let passwordPageItem = createPasswordPageItem() summaryPageItem.next = passwordPageItem summaryPageItem.actionHandler = { item in item.manager?.displayNextItem() } - passwordPageItem.actionHandler = { [weak self] item in - item.manager?.displayActivityIndicator() - self?.sendTransaction(password: passwordPageItem.passwordField.text!) - } - return BLTNItemManager(rootItem: summaryPageItem) }() var token: TokenModel! - var confirmViewController: TransactionConfirmViewController? override func viewDidLoad() { super.viewDidLoad() @@ -60,11 +53,7 @@ class SendTransactionViewController: UITableViewController { } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.identifier == "TransactionConfirmViewController" { - let controller = segue.destination as! TransactionConfirmViewController - controller.paramBuilder = paramBuilder - confirmViewController = controller - } else if segue.identifier == "TransactionGasPriceViewController" { + if segue.identifier == "TransactionGasPriceViewController" { let controller = segue.destination as! TransactionGasPriceViewController controller.param = paramBuilder } @@ -116,22 +105,52 @@ class SendTransactionViewController: UITableViewController { gasCostLabel.text = "\(paramBuilder.txFeeNatural.decimal)\(paramBuilder.nativeCoinSymbol)" } - private func sendTransaction(password: String) { + private func createPasswordPageItem() -> PasswordPageItem { + let passwordPageItem = PasswordPageItem.create() + + passwordPageItem.actionHandler = { [weak self] item in + item.manager?.displayActivityIndicator() + guard let self = self else { + return + } + self.sendTransaction(password: passwordPageItem.passwordField.text!) + } + + return passwordPageItem + } +} + +// MARK: - Send Transaction + +private extension SendTransactionViewController { + func sendTransaction(password: String) { DispatchQueue.global().async { - // TODO: send tx for real - Thread.sleep(forTimeInterval: 3) - DispatchQueue.main.async { - let successPageItem = SuccessPageItem.create(title: "交易已发送") - successPageItem.actionHandler = { item in - item.manager?.dismissBulletin(animated: true) + do { + // TODO: send tx for real + Thread.sleep(forTimeInterval: 1) + throw SendTransactionError.invalidPassword + + DispatchQueue.main.async { + let successPageItem = SuccessPageItem.create(title: "交易已发送") + successPageItem.actionHandler = { item in + item.manager?.dismissBulletin(animated: true) + } + self.bulletinManager.push(item: successPageItem) + } + } catch let error { + DispatchQueue.main.async { + self.bulletinManager.hideActivityIndicator() + /// HACKHACK: Possible a bug of BulletinBoard, but after hiding activity indicator + /// the previous page item's views stop responding to update. To get around that + /// create a new item. Note this would leave more than one password item in the stack. + let passwordPageItem = self.createPasswordPageItem() + self.bulletinManager.push(item: passwordPageItem) + passwordPageItem.errorMessage = error.localizedDescription } - self.bulletinManager.push(item: successPageItem) } } } -} -extension SendTransactionViewController { var isEffectiveTransferInfo: Bool { guard Address.isValid(paramBuilder.to) && paramBuilder.to != "0x" else { Toast.showToast(text: "您的地址错误,请重新输入") From 45432e867dec08f3cf7599b5e45171e2ba395dbf Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 19 Nov 2018 22:28:21 +0900 Subject: [PATCH 106/315] Bring back sending ETH/ERC20 transaction --- Neuron.xcodeproj/project.pbxproj | 2 +- Neuron/Models/TokenModel.swift | 32 ++-- .../SendTransactionViewController.swift | 68 +++++++- .../TransactionHistoryViewController.swift | 8 +- .../Transaction/TransactionParamBuilder.swift | 147 +++--------------- .../Wallet/Home/TokensViewController.swift | 6 +- .../Common/TransactionHistoryService.swift | 6 +- .../Services/Ethereum/EthereumTxSender.swift | 1 + Neuron/Services/Ethereum/TokenProfile.swift | 6 +- 9 files changed, 117 insertions(+), 159 deletions(-) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 9ca7edd8..0621cbf9 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -968,12 +968,12 @@ children = ( 1A75EBFC21A1948F008D484B /* SendTransaction.storyboard */, 1A75EBFE21A194B1008D484B /* SendTransactionViewController.swift */, + 6E4F9617218973F200372D3E /* TransactionParamBuilder.swift */, 89F79670213BD8C40064808A /* Transaction.storyboard */, 89C6145F2141421400DC3DF4 /* TransactionHistoryViewController.swift */, 6E8168F6218874B1007641BA /* TransactionViewController.swift */, 6E8168F821887A31007641BA /* TransactionGasPriceViewController.swift */, 6E4F961A2189A82900372D3E /* TransactionConfirmViewController.swift */, - 6E4F9617218973F200372D3E /* TransactionParamBuilder.swift */, 898A1A2820B7F0FC00ECB465 /* TradeDetailsController.swift */, 898A1A2920B7F0FC00ECB465 /* TradeDetailsController.xib */, 898A1A2C20B8033C00ECB465 /* Views */, diff --git a/Neuron/Models/TokenModel.swift b/Neuron/Models/TokenModel.swift index 88d6869a..5167b884 100644 --- a/Neuron/Models/TokenModel.swift +++ b/Neuron/Models/TokenModel.swift @@ -8,7 +8,13 @@ import Foundation import RealmSwift -import BigInt + +enum TokenType { + case ether + case erc20 + case appChain + case appChainErc20 +} class TokenModel: Object, Decodable { @objc dynamic var tokenBalance = "" @@ -24,7 +30,7 @@ class TokenModel: Object, Decodable { @objc dynamic var identifier = UUID().uuidString // primary key == UUID // defaults false, eth and RPC "getMateData" is true. - @objc dynamic var isNativeToken = false + @objc dynamic var isNativeToken = false // TODO: AppChain ERC20 should not be marked as native token. override class func primaryKey() -> String? { return "identifier" @@ -39,21 +45,15 @@ class TokenModel: Object, Decodable { } var logo: Logo? - enum `Type`: String, Decodable { - case erc20 - case ethereum - case nervos - case nervosErc20 - } - var type: Type { + var type: TokenType { if isNativeToken { if chainId == NativeChainId.ethMainnetChainId { - return .ethereum + return .ether } else { if address != "" { - return .nervosErc20 + return .appChainErc20 } else { - return .nervos + return .appChain } } } else { @@ -62,13 +62,17 @@ class TokenModel: Object, Decodable { } var gasSymbol: String { switch type { - case .erc20, .ethereum: + case .ether, .erc20: return "ETH" - case .nervos, .nervosErc20: + case .appChain, .appChainErc20: return self.symbol } } + var isEthereum: Bool { + return type == .ether || type == .erc20 + } + enum CodingKeys: String, CodingKey { case name case address diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index 95fc8a92..16ac6d94 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -90,7 +90,6 @@ class SendTransactionViewController: UITableViewController { } } - // MARK: - UI func setupUI() { let wallet = WalletRealmTool.getCurrentAppModel().currentWallet! title = "\(token.symbol)转账" @@ -102,7 +101,7 @@ class SendTransactionViewController: UITableViewController { } private func updateGasCost() { - gasCostLabel.text = "\(paramBuilder.txFeeNatural.decimal)\(paramBuilder.nativeCoinSymbol)" + gasCostLabel.text = "\(paramBuilder.txFeeNatural.decimal) \(paramBuilder.nativeCoinSymbol)" } private func createPasswordPageItem() -> PasswordPageItem { @@ -126,21 +125,26 @@ private extension SendTransactionViewController { func sendTransaction(password: String) { DispatchQueue.global().async { do { - // TODO: send tx for real - Thread.sleep(forTimeInterval: 1) - throw SendTransactionError.invalidPassword + let txHash: TxHash + if self.paramBuilder.tokenType == .ether || self.paramBuilder.tokenType == .erc20 { + txHash = try self.sendEthereumTransaction(password: password) + } else { + txHash = try self.sendAppChainTransaction(password: password) + } DispatchQueue.main.async { + // TODO: send back txHash? let successPageItem = SuccessPageItem.create(title: "交易已发送") successPageItem.actionHandler = { item in item.manager?.dismissBulletin(animated: true) + self.navigationController?.popViewController(animated: true) } self.bulletinManager.push(item: successPageItem) } } catch let error { DispatchQueue.main.async { self.bulletinManager.hideActivityIndicator() - /// HACKHACK: Possible a bug of BulletinBoard, but after hiding activity indicator + /// HACKHACKHACK: Possible a bug of BulletinBoard, but after hiding activity indicator /// the previous page item's views stop responding to update. To get around that /// create a new item. Note this would leave more than one password item in the stack. let passwordPageItem = self.createPasswordPageItem() @@ -151,6 +155,58 @@ private extension SendTransactionViewController { } } + func sendEthereumTransaction(password: String) throws -> TxHash { + let keystore = WalletManager.default.keystore(for: paramBuilder.from) + let web3 = EthereumNetwork().getWeb3() + web3.addKeystoreManager(KeystoreManager([keystore])) + + if paramBuilder.tokenType == .ether { + let sender = try EthereumTxSender(web3: web3, from: paramBuilder.from) + return try sender.sendETH( + to: paramBuilder.to, + value: paramBuilder.value, + gasLimit: paramBuilder.gasLimit, + gasPrice: BigUInt(paramBuilder.gasPrice), + data: paramBuilder.data, + password: password + ) + } else { + let sender = try EthereumTxSender(web3: web3, from: paramBuilder.from) + // TODO: estimate gas + return try sender.sendToken( + to: paramBuilder.to, + value: paramBuilder.value, + gasLimit: paramBuilder.gasLimit, + gasPrice: BigUInt(paramBuilder.gasPrice), + contractAddress: paramBuilder.contractAddress, + password: password + ) + } + } + + func sendAppChainTransaction(password: String) throws -> TxHash { + guard let appChainUrl = URL(string: paramBuilder.rpcNode) else { + throw SendTransactionError.invalidAppChainNode + } + if paramBuilder.tokenType == .appChain { + let sender = try AppChainTxSender( + appChain: AppChainNetwork.appChain(url: appChainUrl), + walletManager: WalletManager.default, + from: paramBuilder.from + ) + return try sender.send( + to: paramBuilder.to, + value: paramBuilder.value, + quota: paramBuilder.gasLimit, + data: paramBuilder.data, + chainId: BigUInt(paramBuilder.chainId)!, + password: password + ) + } else { + return "" // TODO: AppChainErc20 not implemented yet. + } + } + var isEffectiveTransferInfo: Bool { guard Address.isValid(paramBuilder.to) && paramBuilder.to != "0x" else { Toast.showToast(text: "您的地址错误,请重新输入") diff --git a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift index 5865f3d5..d23dcf69 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift @@ -22,7 +22,7 @@ class TransactionHistoryViewController: UIViewController, UITableViewDelegate, U var service: TransactionHistoryService? var tokenProfile: TokenProfile? - var tokenType: TokenType = .erc20Token + var tokenType: TokenType = .erc20 var tokenModel: TokenModel! { didSet { guard tokenModel != nil else { return } @@ -189,12 +189,6 @@ class TransactionHistoryViewController: UIViewController, UITableViewDelegate, U } } -enum TokenType { - case ethereumToken - case nervosToken - case erc20Token -} - class TransactionHistoryTableViewCell: UITableViewCell { @IBOutlet weak var addressLabel: UILabel! @IBOutlet weak var dateLabel: UILabel! diff --git a/Neuron/Sections/Transaction/TransactionParamBuilder.swift b/Neuron/Sections/Transaction/TransactionParamBuilder.swift index 033bf060..457ea5d8 100644 --- a/Neuron/Sections/Transaction/TransactionParamBuilder.swift +++ b/Neuron/Sections/Transaction/TransactionParamBuilder.swift @@ -11,10 +11,12 @@ import BigInt /// Prepare tx params. class TransactionParamBuilder: NSObject { - var from: String! + var from: String = "" var to = "" var value: BigUInt = 0 var data = Data() + var contractAddress = "" + var chainId = "" var fetchedGasPrice: BigUInt = 1 // Fetched from node as recommended gas price var gasPrice: BigUInt = 1 { @@ -41,35 +43,33 @@ class TransactionParamBuilder: NSObject { /// Note: this returns true even when native token (ETH) is not enough for tx fee /// when sending ERC20. UI layer should check that. var hasSufficientBalance: Bool { - switch token.type { - case .ethereum, .nervos: + switch tokenType { + case .ether, .appChain: return tokenBalance >= txFee + value - case .erc20, .nervosErc20: + case .erc20, .appChainErc20: return tokenBalance >= value } } - var decimals: Int { - return token.decimals - } - - var symbol: String { - return token.symbol - } - - var nativeCoinSymbol: String { - return token.gasSymbol - } + var tokenType: TokenType + var rpcNode: String = "" + var decimals: Int = 18 + var symbol: String + var nativeCoinSymbol: String - private var token: TokenModel! private var gasCalculator = GasCalculator() init(token: TokenModel) { - super.init() - - self.token = token + tokenType = token.type + decimals = token.decimals + chainId = token.chainId + contractAddress = token.address + symbol = token.symbol + nativeCoinSymbol = token.gasSymbol tokenBalance = Double(token.tokenBalance)!.toAmount(token.decimals) + super.init() + fetchGasPrice() fetchGasLimit() } @@ -80,22 +80,20 @@ class TransactionParamBuilder: NSObject { self.gasPrice = price } - let tokenType = token.type - let tokenNode = token.chainHosts switch tokenType { - case .ethereum, .erc20: + case .ether, .erc20: GasPriceFetcher().fetchGasPrice(then: fetched) - case .nervos, .nervosErc20: - GasPriceFetcher().fetchQuotaPrice(rpcNode: tokenNode, then: fetched) + case .appChain, .appChainErc20: + GasPriceFetcher().fetchQuotaPrice(rpcNode: rpcNode, then: fetched) } } // TODO: implement estimate gas private func fetchGasLimit() { - switch token.type { - case .ethereum, .nervos: + switch tokenType { + case .ether, .appChain: gasLimit = GasCalculator.defaultGasLimit - case .erc20, .nervosErc20: + case .erc20, .appChainErc20: gasLimit = 100_000 } } @@ -105,98 +103,3 @@ class TransactionParamBuilder: NSObject { txFeeNatural = gasCalculator.txFeeNatural } } - -/* -extension TransactionParamBuilder { - class Ethereum: TransactionParamBuilder { - - override func sendTransaction(password: String) { - // TODO: extract this - let keystore = WalletManager.default.keystore(for: from) - let web3 = EthereumNetwork().getWeb3() - web3.addKeystoreManager(KeystoreManager([keystore])) - - do { - // TODO: change amount to string which matches UI input exactly. - guard let value = Web3.Utils.parseToBigUInt(String(amount), units: .eth) else { - throw SendTransactionError.invalidAmountFormat - } - - let sender = try EthereumTxSender(web3: web3, from: from) - let txhash = try sender.sendETH( - to: to, - value: value, - gasLimit: gasLimit, - gasPrice: BigUInt(gasPrice), - data: data, - password: password - ) - // TODO - } catch let error { - // TODO - } - } - } -} - -extension TransactionParamBuilder { - class ERC20: TransactionParamBuilder { - - override func sendTransaction(password: String) { - let keystore = WalletManager.default.keystore(for: from) - let web3 = EthereumNetwork().getWeb3() - web3.addKeystoreManager(KeystoreManager([keystore])) - - do { - // TODO: Get token decimal and convert - guard let value = Web3.Utils.parseToBigUInt(String(amount), units: .eth) else { - throw SendTransactionError.invalidAmountFormat - } - - let sender = try EthereumTxSender(web3: web3, from: from) - // TODO: estimate gas - let txhash = try sender.sendToken( - to: toAddress, - value: value, - gasLimit: gasLimit, - gasPrice: BigUInt(gasPrice), - contractAddress: token.address, - password: password - ) - // TODO - } catch let error { - // TODO - } - } - } -} - -extension TransactionParamBuilder { - class AppChain: TransactionParamBuilder { - - override func sendTransaction(password: String) { - super.sendTransaction(password: password) - do { - guard let appChainUrl = URL(string: token.chainHosts) else { - throw SendTransactionError.invalidAppChainNode - } - guard let value = Web3Utils.parseToBigUInt(String(amount), units: .eth) else { - throw SendTransactionError.invalidAmountFormat - } - let sender = try AppChainTxSender(appChain: AppChainNetwork.appChain(url: appChainUrl), walletManager: WalletManager.default, from: fromAddress) - let txhash = try sender.send( - to: toAddress, - value: value, - quota: gasLimit, - data: data, - chainId: BigUInt(token.chainId)!, - password: password - ) - // TODO - } catch let error { - // TODO - } - } - } -} - */ diff --git a/Neuron/Sections/Wallet/Home/TokensViewController.swift b/Neuron/Sections/Wallet/Home/TokensViewController.swift index 883fe035..06b19b07 100644 --- a/Neuron/Sections/Wallet/Home/TokensViewController.swift +++ b/Neuron/Sections/Wallet/Home/TokensViewController.swift @@ -189,12 +189,12 @@ class TokensViewController: UITableViewController, ErrorOverlayPresentable { controller.tokenModel = model if model.isNativeToken { if model.chainId == NativeChainId.ethMainnetChainId { - controller.tokenType = .ethereumToken + controller.tokenType = .ether } else { - controller.tokenType = .nervosToken + controller.tokenType = .appChain } } else { - controller.tokenType = .erc20Token + controller.tokenType = .erc20 } navigationController?.pushViewController(controller, animated: true) } diff --git a/Neuron/Services/Common/TransactionHistoryService.swift b/Neuron/Services/Common/TransactionHistoryService.swift index 5a5ebcbd..8904b084 100644 --- a/Neuron/Services/Common/TransactionHistoryService.swift +++ b/Neuron/Services/Common/TransactionHistoryService.swift @@ -33,11 +33,11 @@ class TransactionHistoryService { static func service(with token: TokenModel) -> TransactionHistoryService { if token.type == .erc20 { return Erc20(token: token) - } else if token.type == .ethereum { + } else if token.type == .ether { return Ethereum(token: token) - } else if token.type == .nervos { + } else if token.type == .appChain { return AppChain(token: token) - } else if token.type == .nervosErc20 { + } else if token.type == .appChainErc20 { return AppChainErc20(token: token) } else { fatalError() diff --git a/Neuron/Services/Ethereum/EthereumTxSender.swift b/Neuron/Services/Ethereum/EthereumTxSender.swift index 881f3cdc..fa8f453b 100644 --- a/Neuron/Services/Ethereum/EthereumTxSender.swift +++ b/Neuron/Services/Ethereum/EthereumTxSender.swift @@ -50,6 +50,7 @@ class EthereumTxSender { throw SendTransactionError.createTransactionIssue } + transaction.transaction.value = value // Web3swift seems to be having bug setting value return try transaction.sendPromise(password: password).wait().hash } diff --git a/Neuron/Services/Ethereum/TokenProfile.swift b/Neuron/Services/Ethereum/TokenProfile.swift index 2cf3d787..7eefdcf9 100644 --- a/Neuron/Services/Ethereum/TokenProfile.swift +++ b/Neuron/Services/Ethereum/TokenProfile.swift @@ -36,12 +36,12 @@ struct TokenProfile: Decodable { extension TokenModel { func getProfile(complection: @escaping (TokenProfile?) -> Void) { switch type { + case .ether: + getEthereumProfile(complection: complection) case .erc20: getErc20Profile(complection: complection) - case .nervos, .nervosErc20: + case .appChain, .appChainErc20: complection(nil) - case .ethereum: - getEthereumProfile(complection: complection) } } From 6ce52445da5681dd9eda93982e41665be0f37cd6 Mon Sep 17 00:00:00 2001 From: James Chen Date: Tue, 20 Nov 2018 12:50:57 +0900 Subject: [PATCH 107/315] Track sent tx event --- .../Transaction/SendTransactionViewController.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index 16ac6d94..f6dae397 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -136,6 +136,7 @@ private extension SendTransactionViewController { // TODO: send back txHash? let successPageItem = SuccessPageItem.create(title: "交易已发送") successPageItem.actionHandler = { item in + self.track() item.manager?.dismissBulletin(animated: true) self.navigationController?.popViewController(animated: true) } @@ -232,6 +233,17 @@ private extension SendTransactionViewController { present(alert, animated: true, completion: nil) return false } + + func track() { + SensorsAnalytics.Track.transaction( + chainType: paramBuilder.chainId, + currencyType: paramBuilder.symbol, + currencyNumber: Double(amountTextField.text ?? "0")!, + receiveAddress: addressTextField.text ?? "", + outcomeAddress: WalletRealmTool.getCurrentAppModel().currentWallet!.address, + transactionType: .normal + ) + } } extension SendTransactionViewController { From 9f079d2f1fb335b6007d5e2b750e1e60c5f0a14c Mon Sep 17 00:00:00 2001 From: James Chen Date: Tue, 20 Nov 2018 13:16:35 +0900 Subject: [PATCH 108/315] Do not mark TokenModel's virtual var as @objc dynamic and set decimal default value --- Neuron/Models/TokenModel.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Neuron/Models/TokenModel.swift b/Neuron/Models/TokenModel.swift index cd0525ce..7467518b 100644 --- a/Neuron/Models/TokenModel.swift +++ b/Neuron/Models/TokenModel.swift @@ -17,8 +17,6 @@ enum TokenType: String { } class TokenModel: Object, Decodable { - @objc dynamic var tokenBalance = "" - @objc dynamic var currencyAmount = "" @objc dynamic var name = "" @objc dynamic var iconUrl: String? = "" @objc dynamic var address = "" @@ -32,6 +30,9 @@ class TokenModel: Object, Decodable { // defaults false, eth and RPC "getMateData" is true. @objc dynamic var isNativeToken = false // TODO: AppChain ERC20 should not be marked as native token. + var tokenBalance = "0" + var currencyAmount = "0" + override class func primaryKey() -> String? { return "identifier" } From b023c983642c2d1742d58573aad938cfafcc71c7 Mon Sep 17 00:00:00 2001 From: James Chen Date: Tue, 20 Nov 2018 13:23:24 +0900 Subject: [PATCH 109/315] Scan a qr code and enter send transaction view --- .../UIKit/UIStoryboard+Extension.swift | 1 + Neuron/Models/TokenModel.swift | 2 +- .../Transaction/SendTransaction.storyboard | 2 +- .../SendTransactionViewController.swift | 8 +++++++ .../Wallet/Home/WalletViewController.swift | 23 ++++++++----------- 5 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Neuron/Extensions/UIKit/UIStoryboard+Extension.swift b/Neuron/Extensions/UIKit/UIStoryboard+Extension.swift index aa846201..fc9d61bc 100644 --- a/Neuron/Extensions/UIKit/UIStoryboard+Extension.swift +++ b/Neuron/Extensions/UIKit/UIStoryboard+Extension.swift @@ -31,6 +31,7 @@ extension UIStoryboard { case main case overlay case transaction + case sendTransaction case walletManagement case dAppBrowser diff --git a/Neuron/Models/TokenModel.swift b/Neuron/Models/TokenModel.swift index 7467518b..743cdf98 100644 --- a/Neuron/Models/TokenModel.swift +++ b/Neuron/Models/TokenModel.swift @@ -30,7 +30,7 @@ class TokenModel: Object, Decodable { // defaults false, eth and RPC "getMateData" is true. @objc dynamic var isNativeToken = false // TODO: AppChain ERC20 should not be marked as native token. - var tokenBalance = "0" + var tokenBalance = "0" // TODO: Should persist balance, or store them globally. var currencyAmount = "0" override class func primaryKey() -> String? { diff --git a/Neuron/Sections/Transaction/SendTransaction.storyboard b/Neuron/Sections/Transaction/SendTransaction.storyboard index 67cce5c4..bf57589e 100644 --- a/Neuron/Sections/Transaction/SendTransaction.storyboard +++ b/Neuron/Sections/Transaction/SendTransaction.storyboard @@ -197,7 +197,7 @@ - + diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index f6dae397..8bab324a 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -39,6 +39,7 @@ class SendTransactionViewController: UITableViewController { }() var token: TokenModel! + var recipientAddress: String! override func viewDidLoad() { super.viewDidLoad() @@ -48,6 +49,9 @@ class SendTransactionViewController: UITableViewController { self.updateGasCost() }) paramBuilder.from = WalletRealmTool.getCurrentAppModel().currentWallet!.address + if recipientAddress != nil { + paramBuilder.to = recipientAddress + } setupUI() } @@ -97,6 +101,8 @@ class SendTransactionViewController: UITableViewController { walletNameLabel.text = wallet.name walletAddressLabel.text = wallet.address tokenBalanceButton.setTitle("\(token.tokenBalance)\(token.symbol)", for: .normal) + addressTextField.text = paramBuilder.to + updateGasCost() } @@ -272,6 +278,8 @@ extension SendTransactionViewController: UITextFieldDelegate { extension SendTransactionViewController: QRCodeViewControllerDelegate { func didBackQRCodeMessage(codeResult: String) { + // TODO: validate qr code (address or other protocol) + paramBuilder.to = codeResult addressTextField.text = codeResult } } diff --git a/Neuron/Sections/Wallet/Home/WalletViewController.swift b/Neuron/Sections/Wallet/Home/WalletViewController.swift index 82df1461..0e10ae9c 100644 --- a/Neuron/Sections/Wallet/Home/WalletViewController.swift +++ b/Neuron/Sections/Wallet/Home/WalletViewController.swift @@ -12,7 +12,7 @@ import Web3swift import BigInt import PullToRefresh -class WalletViewController: UITableViewController, SelectWalletControllerDelegate, QRCodeViewControllerDelegate { +class WalletViewController: UITableViewController, SelectWalletControllerDelegate { @IBOutlet var titleView: UIView! @IBOutlet var tabHeader: UIView! @IBOutlet weak var tabbedButtonView: TabbedButtonsView! @@ -123,12 +123,10 @@ class WalletViewController: UITableViewController, SelectWalletControllerDelegat private func updateNavigationBar() { if isHeaderViewHidden { -// navigationItem.leftBarButtonItems = [scanBarButtonItem] navigationItem.rightBarButtonItems = [switchWalletButtonItem] navigationItem.title = WalletRealmTool.getCurrentAppModel().currentWallet?.name navigationItem.titleView = nil } else { -// navigationItem.leftBarButtonItems = [scanBarButtonItem] navigationItem.rightBarButtonItems = [requestPaymentButtonItem] navigationItem.titleView = titleView } @@ -190,22 +188,21 @@ class WalletViewController: UITableViewController, SelectWalletControllerDelegat return tabHeader.frame.height } - // MARK: - QRCodeControllerDelegate + deinit { + tableView.removePullToRefresh(at: .top) + } +} + +extension WalletViewController: QRCodeViewControllerDelegate { func didBackQRCodeMessage(codeResult: String) { - guard let token = WalletRealmTool.getCurrentAppModel().nativeTokenList.filter({ (model) -> Bool in - return model.symbol == "ETH" - }).first else { + guard let token = WalletRealmTool.getCurrentAppModel().nativeTokenList.first(where: { $0.symbol == "ETH" }) else { return } - let controller: TransactionViewController = UIStoryboard(name: .transaction).instantiateViewController() + let controller: SendTransactionViewController = UIStoryboard(name: .sendTransaction).instantiateViewController() controller.token = token - controller.didBackQRCodeMessage(codeResult: codeResult) + controller.recipientAddress = codeResult // TODO: At least do address validation here? navigationController?.pushViewController(controller, animated: true) } - - deinit { - tableView.removePullToRefresh(at: .top) - } } extension WalletViewController: TabbedButtonsViewDelegate, UIPageViewControllerDataSource, UIPageViewControllerDelegate, TokensViewControllerDelegate { From ad0654e002a7c470ffea90bd525091ca88250ba6 Mon Sep 17 00:00:00 2001 From: James Chen Date: Tue, 20 Nov 2018 15:27:02 +0900 Subject: [PATCH 110/315] Rebuild message sign flow with BulletinBoard UI --- Neuron.xcodeproj/project.pbxproj | 26 +- .../UIKit/UIStoryboard+Extension.swift | 2 +- .../PageItems/SignMessagePageItem.swift | 19 + .../Dapp/WebView/BrowserViewController.swift | 2 +- .../Dapp/WebView/ContractController.swift | 3 +- .../Dapp/WebView/DAppBrowser.storyboard | 85 +- .../Dapp/WebView/MessageSignController.swift | 135 ++- .../MessageSignShowViewController.swift | 38 - .../Transaction/Transaction.storyboard | 1035 ----------------- .../TransactionConfirmViewController.swift | 4 +- .../Transaction/TransactionHistory.storyboard | 385 ++++++ .../TransactionHistoryViewController.swift | 2 +- .../TransactionViewController.swift | 191 --- .../Wallet/Home/TokensViewController.swift | 2 +- 14 files changed, 495 insertions(+), 1434 deletions(-) create mode 100644 Neuron/Sections/Common/PageItems/SignMessagePageItem.swift delete mode 100644 Neuron/Sections/Dapp/WebView/MessageSignShowViewController.swift delete mode 100644 Neuron/Sections/Transaction/Transaction.storyboard create mode 100644 Neuron/Sections/Transaction/TransactionHistory.storyboard delete mode 100644 Neuron/Sections/Transaction/TransactionViewController.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 22c82ac9..7cdea8e0 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 1A09224321357F3D00CAED5D /* TokensViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A09224221357F3D00CAED5D /* TokensViewController.swift */; }; 1A092245213580C900CAED5D /* NFTViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A092244213580C900CAED5D /* NFTViewController.swift */; }; + 1A1E1B8B21A3CF3A00C24D10 /* SignMessagePageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A1E1B8A21A3CF3A00C24D10 /* SignMessagePageItem.swift */; }; 1A1E841821717EBD00B15E88 /* PasswordValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A1E841721717EBD00B15E88 /* PasswordValidatorTests.swift */; }; 1A207DCF2137B3F7008DC306 /* RequestPaymentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A207DCE2137B3F7008DC306 /* RequestPaymentViewController.swift */; }; 1A207DD12137B412008DC306 /* RequestPayment.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1A207DD02137B412008DC306 /* RequestPayment.storyboard */; }; @@ -51,7 +52,6 @@ 6E4F961B2189A82900372D3E /* TransactionConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4F961A2189A82900372D3E /* TransactionConfirmViewController.swift */; }; 6E7D3838219C173D0031177B /* TransactionDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7D3837219C173D0031177B /* TransactionDetails.swift */; }; 6E7D3842219C47940031177B /* TransactionHistoryPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7D3841219C47940031177B /* TransactionHistoryPresenter.swift */; }; - 6E8168F7218874B1007641BA /* TransactionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8168F6218874B1007641BA /* TransactionViewController.swift */; }; 6E8168F921887A31007641BA /* TransactionGasPriceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8168F821887A31007641BA /* TransactionGasPriceViewController.swift */; }; 6E8173B3218D55A10078A2CF /* InputTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8173B2218D55A10078A2CF /* InputTextViewController.swift */; }; 6E83CB8D216F573100029324 /* ProductAgreementViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E83CB88216F573100029324 /* ProductAgreementViewController.swift */; }; @@ -104,7 +104,6 @@ 8928C27A20B2A61900C3103E /* WalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8928C27820B2A61900C3103E /* WalletViewController.swift */; }; 8928C2A320B41BCA00C3103E /* SelectWalletController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8928C2A120B41BCA00C3103E /* SelectWalletController.swift */; }; 892A1985215A55F400B2293D /* Double+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892A1984215A55F400B2293D /* Double+Extension.swift */; }; - 893532F4217DECF400404C0B /* MessageSignShowViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 893532F3217DECF400404C0B /* MessageSignShowViewController.swift */; }; 8935BA55216EE65500C37263 /* WKWebViewConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8935BA54216EE65500C37263 /* WKWebViewConfiguration.swift */; }; 8935BA57216EEDEC00C37263 /* WKWebViewConfigConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8935BA56216EEDEC00C37263 /* WKWebViewConfigConstants.swift */; }; 8935BA59216EFCE700C37263 /* neuron.js in Resources */ = {isa = PBXBuildFile; fileRef = 8935BA58216EFCE700C37263 /* neuron.js */; }; @@ -189,7 +188,7 @@ 89D935252139363A002FAEEC /* TokenTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D935242139363A002FAEEC /* TokenTableViewCell.swift */; }; 89F7966D213A6B680064808A /* ERC721TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F7966C213A6B680064808A /* ERC721TableViewCell.swift */; }; 89F7966F213BD5680064808A /* Assets.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 89F7966E213BD5680064808A /* Assets.storyboard */; }; - 89F79671213BD8C40064808A /* Transaction.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 89F79670213BD8C40064808A /* Transaction.storyboard */; }; + 89F79671213BD8C40064808A /* TransactionHistory.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 89F79670213BD8C40064808A /* TransactionHistory.storyboard */; }; 89F9E73E20DEBDE8009E68D4 /* ExportKeystoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F9E73C20DEBDE8009E68D4 /* ExportKeystoreController.swift */; }; 89F9E73F20DEBDE8009E68D4 /* ExportKeystoreController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 89F9E73D20DEBDE8009E68D4 /* ExportKeystoreController.xib */; }; 89FC542D20D4142D00D5D27C /* SkipBackupFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FC542C20D4142D00D5D27C /* SkipBackupFiles.swift */; }; @@ -220,6 +219,7 @@ /* Begin PBXFileReference section */ 1A09224221357F3D00CAED5D /* TokensViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokensViewController.swift; sourceTree = ""; }; 1A092244213580C900CAED5D /* NFTViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFTViewController.swift; sourceTree = ""; }; + 1A1E1B8A21A3CF3A00C24D10 /* SignMessagePageItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignMessagePageItem.swift; sourceTree = ""; }; 1A1E841721717EBD00B15E88 /* PasswordValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordValidatorTests.swift; sourceTree = ""; }; 1A207DCE2137B3F7008DC306 /* RequestPaymentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestPaymentViewController.swift; sourceTree = ""; }; 1A207DD02137B412008DC306 /* RequestPayment.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = RequestPayment.storyboard; sourceTree = ""; }; @@ -265,7 +265,6 @@ 6E4F961A2189A82900372D3E /* TransactionConfirmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionConfirmViewController.swift; sourceTree = ""; }; 6E7D3837219C173D0031177B /* TransactionDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetails.swift; sourceTree = ""; }; 6E7D3841219C47940031177B /* TransactionHistoryPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistoryPresenter.swift; sourceTree = ""; }; - 6E8168F6218874B1007641BA /* TransactionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionViewController.swift; sourceTree = ""; }; 6E8168F821887A31007641BA /* TransactionGasPriceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionGasPriceViewController.swift; sourceTree = ""; }; 6E8173B2218D55A10078A2CF /* InputTextViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputTextViewController.swift; sourceTree = ""; }; 6E83CB88216F573100029324 /* ProductAgreementViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProductAgreementViewController.swift; sourceTree = ""; }; @@ -320,7 +319,6 @@ 8928C27820B2A61900C3103E /* WalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletViewController.swift; sourceTree = ""; }; 8928C2A120B41BCA00C3103E /* SelectWalletController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletController.swift; sourceTree = ""; }; 892A1984215A55F400B2293D /* Double+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Double+Extension.swift"; sourceTree = ""; }; - 893532F3217DECF400404C0B /* MessageSignShowViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSignShowViewController.swift; sourceTree = ""; }; 8935BA54216EE65500C37263 /* WKWebViewConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKWebViewConfiguration.swift; sourceTree = ""; }; 8935BA56216EEDEC00C37263 /* WKWebViewConfigConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKWebViewConfigConstants.swift; sourceTree = ""; }; 8935BA58216EFCE700C37263 /* neuron.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = neuron.js; sourceTree = ""; }; @@ -412,7 +410,7 @@ 89D935242139363A002FAEEC /* TokenTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenTableViewCell.swift; sourceTree = ""; }; 89F7966C213A6B680064808A /* ERC721TableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ERC721TableViewCell.swift; sourceTree = ""; }; 89F7966E213BD5680064808A /* Assets.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Assets.storyboard; sourceTree = ""; }; - 89F79670213BD8C40064808A /* Transaction.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Transaction.storyboard; sourceTree = ""; }; + 89F79670213BD8C40064808A /* TransactionHistory.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TransactionHistory.storyboard; sourceTree = ""; }; 89F9E73C20DEBDE8009E68D4 /* ExportKeystoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportKeystoreController.swift; sourceTree = ""; }; 89F9E73D20DEBDE8009E68D4 /* ExportKeystoreController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExportKeystoreController.xib; sourceTree = ""; }; 89FC542C20D4142D00D5D27C /* SkipBackupFiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SkipBackupFiles.swift; sourceTree = ""; }; @@ -495,6 +493,7 @@ children = ( 1A60079621A274D000C7B712 /* SendTransactionSummaryView.xib */, 1A60079B21A28B8C00C7B712 /* TxSummaryPageItem.swift */, + 1A1E1B8A21A3CF3A00C24D10 /* SignMessagePageItem.swift */, 1A60079D21A28C1400C7B712 /* PasswordPageItem.swift */, 1A60079F21A29C2C00C7B712 /* SuccessPageItem.swift */, 1A6007A121A2AA9A00C7B712 /* PageItemAppearance.swift */, @@ -660,7 +659,6 @@ 89C25C6B20BD421E00007EC1 /* ContractController.swift */, 8972FD0E218B54A100C27147 /* AdvancedViewController.swift */, 8964A17B20BE7F750086848F /* MessageSignController.swift */, - 893532F3217DECF400404C0B /* MessageSignShowViewController.swift */, ); path = WebView; sourceTree = ""; @@ -754,11 +752,11 @@ 8928C22620AE7C6200C3103E /* Sections */ = { isa = PBXGroup; children = ( + 8928C22A20AE7CB500C3103E /* Common */, 6E83CB87216F573100029324 /* Guide */, 8928C22720AE7CB500C3103E /* Dapp */, 8928C27320B29ABB00C3103E /* Wallet */, 8922685820DE4EA50021A85F /* Settings */, - 8928C22A20AE7CB500C3103E /* Common */, 898A1A1D20B7B4F300ECB465 /* Transaction */, 6E9D7CBB216B37E90044176D /* Authentication */, ); @@ -989,12 +987,11 @@ 1A75EBFC21A1948F008D484B /* SendTransaction.storyboard */, 1A75EBFE21A194B1008D484B /* SendTransactionViewController.swift */, 1AE3145021A2F5A300F92B2A /* TransactionParamBuilder.swift */, - 89F79670213BD8C40064808A /* Transaction.storyboard */, - 89C6145F2141421400DC3DF4 /* TransactionHistoryViewController.swift */, - 6E7D3841219C47940031177B /* TransactionHistoryPresenter.swift */, - 6E8168F6218874B1007641BA /* TransactionViewController.swift */, 6E8168F821887A31007641BA /* TransactionGasPriceViewController.swift */, 6E4F961A2189A82900372D3E /* TransactionConfirmViewController.swift */, + 89F79670213BD8C40064808A /* TransactionHistory.storyboard */, + 89C6145F2141421400DC3DF4 /* TransactionHistoryViewController.swift */, + 6E7D3841219C47940031177B /* TransactionHistoryPresenter.swift */, 898A1A2820B7F0FC00ECB465 /* TradeDetailsController.swift */, 898A1A2920B7F0FC00ECB465 /* TradeDetailsController.xib */, 898A1A2C20B8033C00ECB465 /* Views */, @@ -1195,7 +1192,7 @@ 896D043420AE69C5002CFF6A /* Assets.xcassets in Resources */, 896D043220AE69C1002CFF6A /* Main.storyboard in Resources */, 1A207DD72137D8F5008DC306 /* TabbedButtonsView.xib in Resources */, - 89F79671213BD8C40064808A /* Transaction.storyboard in Resources */, + 89F79671213BD8C40064808A /* TransactionHistory.storyboard in Resources */, 89B01A24216DAC5A00BFAAF4 /* DAppBrowser.storyboard in Resources */, 8935BA59216EFCE700C37263 /* neuron.js in Resources */, 1A9204E421634212004B54DC /* ToastActivityView.xib in Resources */, @@ -1370,13 +1367,13 @@ 89733044217ED041001B8D93 /* ETHSignMessageService.swift in Sources */, 1A69465C21916BE5000093A2 /* EthereumTxSender.swift in Sources */, 898889CA2136528B00D04AA8 /* PrivatekeyViewController.swift in Sources */, + 1A1E1B8B21A3CF3A00C24D10 /* SignMessagePageItem.swift in Sources */, 8935BA55216EE65500C37263 /* WKWebViewConfiguration.swift in Sources */, 6EABFB122199520F00305ED5 /* DAppTakePhotoMessageHandler.swift in Sources */, 6E7D3838219C173D0031177B /* TransactionDetails.swift in Sources */, 898A19F020B5176A00ECB465 /* WalletDetailController.swift in Sources */, 8928C24320AE908300C3103E /* MainViewController.swift in Sources */, 8964A19620C12FB70086848F /* ButtonTagView.swift in Sources */, - 893532F4217DECF400404C0B /* MessageSignShowViewController.swift in Sources */, 6E83CB90216F573100029324 /* GuideCollectionViewCell.swift in Sources */, 1AEBF3E4219BA1E700653FF5 /* BigUInt+Extension.swift in Sources */, 1A6007A221A2AA9A00C7B712 /* PageItemAppearance.swift in Sources */, @@ -1432,7 +1429,6 @@ 89FE21DA20EDB10900A09302 /* CustomERC20TokenService.swift in Sources */, 8913437120C78F1000A17AEF /* WalletModel.swift in Sources */, 6E9D7CBE216B386F0044176D /* AuthenticationViewController.swift in Sources */, - 6E8168F7218874B1007641BA /* TransactionViewController.swift in Sources */, 6E9D7CC5216B490D0044176D /* AuthDeviceViewController.swift in Sources */, 89F7966D213A6B680064808A /* ERC721TableViewCell.swift in Sources */, 8964A19320C115A70086848F /* VerifyMnemonicViewController.swift in Sources */, diff --git a/Neuron/Extensions/UIKit/UIStoryboard+Extension.swift b/Neuron/Extensions/UIKit/UIStoryboard+Extension.swift index fc9d61bc..309ee86a 100644 --- a/Neuron/Extensions/UIKit/UIStoryboard+Extension.swift +++ b/Neuron/Extensions/UIKit/UIStoryboard+Extension.swift @@ -30,7 +30,7 @@ extension UIStoryboard { case addWallet case main case overlay - case transaction + case transactionHistory case sendTransaction case walletManagement case dAppBrowser diff --git a/Neuron/Sections/Common/PageItems/SignMessagePageItem.swift b/Neuron/Sections/Common/PageItems/SignMessagePageItem.swift new file mode 100644 index 00000000..f6fdd363 --- /dev/null +++ b/Neuron/Sections/Common/PageItems/SignMessagePageItem.swift @@ -0,0 +1,19 @@ +// +// SignMessagePageItem.swift +// Neuron +// +// Created by James Chen on 2018/11/20. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import BLTNBoard + +class SignMessagePageItem: BLTNPageItem { + static func create() -> SignMessagePageItem { + let item = SignMessagePageItem(title: "DApp签名信息确认") + item.appearance = PageItemAppearance.default + item.actionButtonTitle = "确认" + return item + } +} diff --git a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift index d21b4abb..cff033c6 100644 --- a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift @@ -212,7 +212,7 @@ extension BrowserViewController { controller.modalPresentationStyle = .overCurrentContext controller.delegate = self controller.dappCommonModel = dappCommonModel - present(controller, animated: false, completion: nil) + present(controller, animated: false) } } diff --git a/Neuron/Sections/Dapp/WebView/ContractController.swift b/Neuron/Sections/Dapp/WebView/ContractController.swift index 248cb597..f34ad0ff 100644 --- a/Neuron/Sections/Dapp/WebView/ContractController.swift +++ b/Neuron/Sections/Dapp/WebView/ContractController.swift @@ -9,7 +9,6 @@ import UIKit import AppChain import BigInt -import struct BigInt.BigUInt import Web3swift import EthereumAddress @@ -218,7 +217,7 @@ class ContractController: UITableViewController { // TODO: set gas price // paramBuilder.gasPrice = gasPrice.words.first! } - let controller: TransactionConfirmViewController = UIStoryboard(name: .transaction).instantiateViewController() + let controller: TransactionConfirmViewController = UIStoryboard(name: .transactionHistory).instantiateViewController() controller.modalPresentationStyle = .overCurrentContext controller.paramBuilder = paramBuilder present(controller, animated: false, completion: nil) diff --git a/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard b/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard index 88d1f393..b982dd83 100644 --- a/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard +++ b/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard @@ -642,14 +642,6 @@ - - - - - - - - @@ -660,85 +652,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/Neuron/Sections/Dapp/WebView/MessageSignController.swift b/Neuron/Sections/Dapp/WebView/MessageSignController.swift index dc9d1598..3a849760 100644 --- a/Neuron/Sections/Dapp/WebView/MessageSignController.swift +++ b/Neuron/Sections/Dapp/WebView/MessageSignController.swift @@ -7,89 +7,102 @@ // import UIKit +import BLTNBoard protocol MessageSignControllerDelegate: class { func messageSignCallBackWebView(id: Int, value: String, error: DAppError?) } -class MessageSignController: UIViewController, TransactionConfirmViewControllerDelegate { +class MessageSignController: UIViewController { var dappCommonModel: DAppCommonModel! weak var delegate: MessageSignControllerDelegate? private var chainType: ChainType = .appChain - private var tokenModel = TokenModel() - private var messageSignShowViewController: MessageSignShowViewController! - private var confirmController: TransactionConfirmViewController! - override func viewDidLoad() { - super.viewDidLoad() - messageSignShowViewController = storyboard!.instantiateViewController(withIdentifier: "messageSignShowViewController") as? MessageSignShowViewController - messageSignShowViewController.delegate = self - - let confirmController: TransactionConfirmViewController = UIStoryboard(name: .transaction).instantiateViewController() - confirmController.delegate = self - confirmController.contentViewController = messageSignShowViewController - self.confirmController = confirmController - present(confirmController, animated: false, completion: nil) - - setUIData() - } - - func transactionConfirmWalletPassword(_ controller: TransactionConfirmViewController, password: String) { - switch chainType { - case .appChain: - appChainSign(password: password) - case .eth: - ethSign(password: password) + private lazy var messagePageItem: SignMessagePageItem = { + return SignMessagePageItem.create() + }() + private lazy var bulletinManager: BLTNItemManager = { + let passwordPageItem = createPasswordPageItem() + messagePageItem.next = passwordPageItem + messagePageItem.actionHandler = { item in + item.manager?.displayNextItem() + } + messagePageItem.dismissalHandler = { [weak self] item in + self?.cancel() } - } - func transactionCanceled(_ controller: TransactionConfirmViewController) { - delegate?.messageSignCallBackWebView(id: self.dappCommonModel!.id, value: "", error: DAppError.userCanceled) - dismiss(animated: false, completion: nil) - } + return BLTNItemManager(rootItem: messagePageItem) + }() + + override func viewDidLoad() { + super.viewDidLoad() - func setUIData() { + let dataText: String if dappCommonModel.chainType == "AppChain" { chainType = .appChain - messageSignShowViewController.dataText = dappCommonModel.appChain?.data ?? "" + dataText = dappCommonModel.appChain?.data ?? "" } else { chainType = .eth - messageSignShowViewController.dataText = dappCommonModel.eth?.data ?? "" + dataText = dappCommonModel.eth?.data ?? "" } - getTokenModel() + messagePageItem.descriptionText = String(decoding: Data.fromHex(dataText)!, as: UTF8.self) } - func getTokenModel() { - let appModel = WalletRealmTool.getCurrentAppModel() - switch chainType { - case .appChain: - let result = appModel.nativeTokenList.filter { return Int($0.chainId) == self.dappCommonModel.appChain!.chainId} - guard let model = result.first else { - return - } - self.tokenModel = model - case .eth: - let result = appModel.nativeTokenList.filter { return Int($0.chainId) == self.dappCommonModel.eth!.chainId} - guard let model = result.first else { + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + bulletinManager.showBulletin(above: self) + } + + private func createPasswordPageItem() -> PasswordPageItem { + let passwordPageItem = PasswordPageItem.create() + + passwordPageItem.actionHandler = { [weak self] item in + item.manager?.displayActivityIndicator() + guard let self = self else { return } - self.tokenModel = model + self.signMessage(password: passwordPageItem.passwordField.text!) } + + passwordPageItem.dismissalHandler = { [weak self] item in + self?.cancel() + } + + return passwordPageItem + } + + private func finish(signed: String) { + delegate?.messageSignCallBackWebView(id: dappCommonModel!.id, value: signed, error: nil) + bulletinManager.dismissBulletin() + dismiss(animated: false) } -} -extension MessageSignController: MessageSignShowViewControllerDelegate { - func clickAgreeButton() { - confirmController.confirmTransactionInfo() + private func cancel() { + delegate?.messageSignCallBackWebView(id: dappCommonModel!.id, value: "", error: DAppError.userCanceled) + dismiss(animated: false) } - func clickRejectButton() { - confirmController.dismiss() + private func showSignError(_ error: String) { + bulletinManager.hideActivityIndicator() + let passwordPageItem = createPasswordPageItem() + bulletinManager.push(item: passwordPageItem) + passwordPageItem.errorMessage = error } } // MARK: - Sign -extension MessageSignController { + +private extension MessageSignController { + func signMessage(password: String) { + switch chainType { + case .appChain: + appChainSign(password: password) + case .eth: + ethSign(password: password) + } + } + func ethSign(password: String) { switch dappCommonModel.name { case .signMessage: @@ -113,38 +126,34 @@ extension MessageSignController { } func ethSignPersonalMessage(password: String) { - Toast.showHUD() ETHSignMessageService.signPersonal(message: dappCommonModel.eth?.data ?? "", password: password) { (result) in Toast.hideHUD() switch result { case .success(let signed): - self.delegate?.messageSignCallBackWebView(id: self.dappCommonModel!.id, value: signed, error: nil) - self.view.removeFromSuperview() - self.confirmController.dismiss() + self.finish(signed: signed) case .error(let error): - Toast.showToast(text: error.localizedDescription) + self.showSignError(error.localizedDescription) } } } func ethSignMessage(password: String) { - Toast.showHUD() ETHSignMessageService.sign(message: dappCommonModel.eth?.data ?? "", password: password) { (result) in Toast.hideHUD() switch result { case .success(let signed): - self.delegate?.messageSignCallBackWebView(id: self.dappCommonModel!.id, value: signed, error: nil) - self.view.removeFromSuperview() - self.confirmController.dismiss() + self.finish(signed: signed) case .error(let error): - Toast.showToast(text: error.localizedDescription) + self.showSignError(error.localizedDescription) } } } func appChainSignMessage(password: String) { + // TODO: connect this } func appChainSignPersonalMessage(password: String) { + // TODO: connect this } } diff --git a/Neuron/Sections/Dapp/WebView/MessageSignShowViewController.swift b/Neuron/Sections/Dapp/WebView/MessageSignShowViewController.swift deleted file mode 100644 index e91598a7..00000000 --- a/Neuron/Sections/Dapp/WebView/MessageSignShowViewController.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// MessageSignShowViewController.swift -// Neuron -// -// Created by XiaoLu on 2018/10/22. -// Copyright © 2018 Cryptape. All rights reserved. -// - -import UIKit - -protocol MessageSignShowViewControllerDelegate: class { - func clickAgreeButton() - func clickRejectButton() -} - -class MessageSignShowViewController: UIViewController { - @IBOutlet weak var dataLabel: UILabel! - var dataText = "" { - didSet { - dataLabel.text = String(decoding: Data.fromHex(dataText)!, as: UTF8.self) - } - } - - weak var delegate: MessageSignShowViewControllerDelegate? - - override func viewDidLoad() { - super.viewDidLoad() - - } - - @IBAction func agreeAction(_ sender: UIButton) { - delegate?.clickAgreeButton() - } - - @IBAction func rejectAction(_ sender: UIButton) { - delegate?.clickRejectButton() - } -} diff --git a/Neuron/Sections/Transaction/Transaction.storyboard b/Neuron/Sections/Transaction/Transaction.storyboard deleted file mode 100644 index 86e5fcee..00000000 --- a/Neuron/Sections/Transaction/Transaction.storyboard +++ /dev/null @@ -1,1035 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Neuron/Sections/Transaction/TransactionConfirmViewController.swift b/Neuron/Sections/Transaction/TransactionConfirmViewController.swift index 06f29c78..622843d2 100644 --- a/Neuron/Sections/Transaction/TransactionConfirmViewController.swift +++ b/Neuron/Sections/Transaction/TransactionConfirmViewController.swift @@ -23,7 +23,7 @@ class TransactionConfirmViewController: UIViewController, TransactionConfirmSend @IBOutlet weak var containView: UIView! var paramBuilder: TransactionParamBuilder! { didSet { - let controller: TransactionConfirmInfoViewController = UIStoryboard(name: .transaction).instantiateViewController() + let controller: TransactionConfirmInfoViewController = UIStoryboard(name: .transactionHistory).instantiateViewController() controller.paramBuilder = paramBuilder controller.delegate = self contentViewController = controller @@ -97,7 +97,7 @@ class TransactionConfirmViewController: UIViewController, TransactionConfirmSend } func confirmTransactionInfo() { - let controller: TransactionConfirmSendViewController = UIStoryboard(name: .transaction).instantiateViewController() + let controller: TransactionConfirmSendViewController = UIStoryboard(name: .transactionHistory).instantiateViewController() controller.delegate = self contentViewController = controller } diff --git a/Neuron/Sections/Transaction/TransactionHistory.storyboard b/Neuron/Sections/Transaction/TransactionHistory.storyboard new file mode 100644 index 00000000..26e641df --- /dev/null +++ b/Neuron/Sections/Transaction/TransactionHistory.storyboard @@ -0,0 +1,385 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift index d043b6d0..b9bae074 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift @@ -1,5 +1,5 @@ // -// TransactionViewController.swift +// TransactionHistoryViewController.swift // Neuron // // Created by XiaoLu on 2018/9/6. diff --git a/Neuron/Sections/Transaction/TransactionViewController.swift b/Neuron/Sections/Transaction/TransactionViewController.swift deleted file mode 100644 index 3bdba405..00000000 --- a/Neuron/Sections/Transaction/TransactionViewController.swift +++ /dev/null @@ -1,191 +0,0 @@ -// -// TransactionViewController.swift -// Neuron -// -// Created by 晨风 on 2018/10/30. -// Copyright © 2018 Cryptape. All rights reserved. -// - -import UIKit -import BigInt -import AppChain -import Web3swift -import EthereumAddress - -class TransactionViewController: UITableViewController { - @IBOutlet weak var walletIconView: UIImageView! - @IBOutlet weak var walletNameLabel: UILabel! - @IBOutlet weak var walletAddressLabel: UILabel! - @IBOutlet weak var tokenBalanceButton: UIButton! - @IBOutlet weak var amountTextField: UITextField! - @IBOutlet weak var gasCostLabel: UILabel! - @IBOutlet weak var addressTextField: UITextField! - - private var paramBuilder: TransactionParamBuilder! - private var observers = [NSKeyValueObservation]() - var token: TokenModel! - var confirmViewController: TransactionConfirmViewController? - - override func viewDidLoad() { - super.viewDidLoad() - - paramBuilder = TransactionParamBuilder(token: token) - observers.append(paramBuilder.observe(\.txFeeNatural, options: [.initial]) { (_, _) in - self.updateGasCost() - }) - paramBuilder.from = WalletRealmTool.getCurrentAppModel().currentWallet!.address - - setupUI() - } - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.identifier == "TransactionConfirmViewController" { - let controller = segue.destination as! TransactionConfirmViewController - controller.paramBuilder = paramBuilder - confirmViewController = controller - } else if segue.identifier == "TransactionGasPriceViewController" { - let controller = segue.destination as! TransactionGasPriceViewController - controller.param = paramBuilder - } - } - - // MARK: - Event - @IBAction func next(_ sender: Any) { - let amount = Double(amountTextField.text!) ?? 0.0 - paramBuilder.value = amount.toAmount(token.decimals) - paramBuilder.to = addressTextField.text! - - if isEffectiveTransferInfo { - performSegue(withIdentifier: "TransactionConfirmViewController", sender: nil) - } - } - - @IBAction func scanQRCode() { - UIApplication.shared.keyWindow?.endEditing(true) - let qrCodeViewController = QRCodeViewController() - qrCodeViewController.delegate = self - navigationController?.pushViewController(qrCodeViewController, animated: true) - } - - @IBAction func transactionAvailableBalance() { - // TODO: FIXME: erc20 token requires ETH balance for tx fee - let amount = Double(token.tokenBalance)! - paramBuilder.txFeeNatural - amountTextField.text = "\(amount)" - paramBuilder.value = amount.toAmount(token.decimals) - guard paramBuilder.hasSufficientBalance else { - Toast.showToast(text: "请确保账户剩余\(token.gasSymbol)高于矿工费用,以便顺利完成转账~") - return - } - } - - // TODO: tx sent - /* - func transactionCompletion(_ transactionService: TransactionParamBuilder, result: TransactionParamBuilder.Result) { - switch result { - case .error(let error): - Toast.showToast(text: error.localizedDescription) - default: - Toast.showToast(text: "转账成功,请稍后刷新查看") - confirmViewController?.dismiss() - navigationController?.popViewController(animated: true) - - SensorsAnalytics.Track.transaction( - chainType: token.chainId, - currencyType: token.symbol, - currencyNumber: Double(amountTextField.text ?? "0")!, - receiveAddress: addressTextField.text ?? "", - outcomeAddress: WalletRealmTool.getCurrentAppModel().currentWallet!.address, - transactionType: .normal - ) - } - }*/ - - // MARK: - UI - func setupUI() { - let wallet = WalletRealmTool.getCurrentAppModel().currentWallet! - title = "\(token.symbol)转账" - walletIconView.image = UIImage(data: wallet.iconData) - walletNameLabel.text = wallet.name - walletAddressLabel.text = wallet.address - tokenBalanceButton.setTitle("\(token.tokenBalance)\(token.symbol)", for: .normal) - updateGasCost() - } - - private func updateGasCost() { - gasCostLabel.text = "\(paramBuilder.txFeeNatural.decimal)\(paramBuilder.nativeCoinSymbol)" - } -} - -extension TransactionViewController { - var isEffectiveTransferInfo: Bool { - guard Address.isValid(paramBuilder.to) && paramBuilder.to != "0x" else { - Toast.showToast(text: "您的地址错误,请重新输入") - return false - } - - // TODO: FIXME: erc20 requires eth balance as tx fee - if paramBuilder.hasSufficientBalance { - return true - } - - if paramBuilder.tokenBalance <= BigUInt(0) { - Toast.showToast(text: "请确保账户剩余\(token.gasSymbol)高于矿工费用,以便顺利完成转账~") - return false - } - let alert = UIAlertController(title: "您输入的金额超过您的余额,是否全部转出?", message: "", preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "确认", style: .default, handler: { (_) in - self.transactionAvailableBalance() - })) - alert.addAction(UIAlertAction(title: "取消", style: .destructive, handler: { (_) in - self.amountTextField.text = "" - })) - present(alert, animated: true, completion: nil) - return false - } -} - -extension TransactionViewController: UITextFieldDelegate { - func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - if textField == amountTextField { - let character: String - if (textField.text?.contains("."))! { - character = "0123456789" - } else { - character = "0123456789." - } - guard CharacterSet(charactersIn: character).isSuperset(of: CharacterSet(charactersIn: string)) else { - return false - } - return true - } - return true - } -} - -extension TransactionViewController: QRCodeViewControllerDelegate { - func didBackQRCodeMessage(codeResult: String) { - addressTextField.text = codeResult - } -} - -extension TransactionViewController { - override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { - return indexPath.row == 2 - } - - override func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? { - return indexPath.row == 2 ? indexPath : nil - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = super.tableView(tableView, cellForRowAt: indexPath) - if indexPath.section == 0 && indexPath.row == 2 { - cell.accessoryType = .disclosureIndicator - } - return cell - } -} diff --git a/Neuron/Sections/Wallet/Home/TokensViewController.swift b/Neuron/Sections/Wallet/Home/TokensViewController.swift index 06b19b07..b74ebdae 100644 --- a/Neuron/Sections/Wallet/Home/TokensViewController.swift +++ b/Neuron/Sections/Wallet/Home/TokensViewController.swift @@ -184,7 +184,7 @@ class TokensViewController: UITableViewController, ErrorOverlayPresentable { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - let controller: TransactionHistoryViewController = UIStoryboard(name: .transaction).instantiateViewController() + let controller: TransactionHistoryViewController = UIStoryboard(name: .transactionHistory).instantiateViewController() let model = tokenArray[indexPath.row] controller.tokenModel = model if model.isNativeToken { From 1801ee8e77f9bf92e7964fca8a672012de987ee1 Mon Sep 17 00:00:00 2001 From: James Chen Date: Tue, 20 Nov 2018 16:01:43 +0900 Subject: [PATCH 111/315] Delete TransactionConfirmViewController --- Neuron.xcodeproj/project.pbxproj | 4 - .../Dapp/WebView/ContractController.swift | 7 +- .../TransactionConfirmViewController.swift | 199 ------------------ .../Transaction/TransactionHistory.storyboard | 95 --------- 4 files changed, 1 insertion(+), 304 deletions(-) delete mode 100644 Neuron/Sections/Transaction/TransactionConfirmViewController.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 7cdea8e0..b41ae347 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -49,7 +49,6 @@ 6E3B2B71219EDA230095257D /* AppChain+TransactionDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3B2B70219EDA230095257D /* AppChain+TransactionDetails.swift */; }; 6E3B2B73219EDB870095257D /* AppChain+TransactionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3B2B72219EDB870095257D /* AppChain+TransactionStatus.swift */; }; 6E3B2B75219EDE200095257D /* TransactionStatusManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3B2B74219EDE200095257D /* TransactionStatusManager.swift */; }; - 6E4F961B2189A82900372D3E /* TransactionConfirmViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4F961A2189A82900372D3E /* TransactionConfirmViewController.swift */; }; 6E7D3838219C173D0031177B /* TransactionDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7D3837219C173D0031177B /* TransactionDetails.swift */; }; 6E7D3842219C47940031177B /* TransactionHistoryPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7D3841219C47940031177B /* TransactionHistoryPresenter.swift */; }; 6E8168F921887A31007641BA /* TransactionGasPriceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8168F821887A31007641BA /* TransactionGasPriceViewController.swift */; }; @@ -262,7 +261,6 @@ 6E3B2B70219EDA230095257D /* AppChain+TransactionDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppChain+TransactionDetails.swift"; sourceTree = ""; }; 6E3B2B72219EDB870095257D /* AppChain+TransactionStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppChain+TransactionStatus.swift"; sourceTree = ""; }; 6E3B2B74219EDE200095257D /* TransactionStatusManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionStatusManager.swift; sourceTree = ""; }; - 6E4F961A2189A82900372D3E /* TransactionConfirmViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionConfirmViewController.swift; sourceTree = ""; }; 6E7D3837219C173D0031177B /* TransactionDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionDetails.swift; sourceTree = ""; }; 6E7D3841219C47940031177B /* TransactionHistoryPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistoryPresenter.swift; sourceTree = ""; }; 6E8168F821887A31007641BA /* TransactionGasPriceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionGasPriceViewController.swift; sourceTree = ""; }; @@ -988,7 +986,6 @@ 1A75EBFE21A194B1008D484B /* SendTransactionViewController.swift */, 1AE3145021A2F5A300F92B2A /* TransactionParamBuilder.swift */, 6E8168F821887A31007641BA /* TransactionGasPriceViewController.swift */, - 6E4F961A2189A82900372D3E /* TransactionConfirmViewController.swift */, 89F79670213BD8C40064808A /* TransactionHistory.storyboard */, 89C6145F2141421400DC3DF4 /* TransactionHistoryViewController.swift */, 6E7D3841219C47940031177B /* TransactionHistoryPresenter.swift */, @@ -1491,7 +1488,6 @@ 8981212A218D3161000813C8 /* QRCodeViewController.swift in Sources */, 8964A18D20C0E4BE0086848F /* GenerateMnemonicController.swift in Sources */, 6EABFB0B21991BC200305ED5 /* DAppQRCodeMessageHandler.swift in Sources */, - 6E4F961B2189A82900372D3E /* TransactionConfirmViewController.swift in Sources */, 89B296EB20F4CB6B008558A7 /* TransactionModel.swift in Sources */, 1A6946602192A0B0000093A2 /* GasCalculator.swift in Sources */, 89D84CED2148C056006B0287 /* NFTFooterReusableView.swift in Sources */, diff --git a/Neuron/Sections/Dapp/WebView/ContractController.swift b/Neuron/Sections/Dapp/WebView/ContractController.swift index f34ad0ff..083cfc91 100644 --- a/Neuron/Sections/Dapp/WebView/ContractController.swift +++ b/Neuron/Sections/Dapp/WebView/ContractController.swift @@ -29,7 +29,6 @@ class ContractController: UITableViewController { private var tokenModel = TokenModel() var advancedViewController: AdvancedViewController! weak var delegate: ContractControllerDelegate? - var confirmViewController: TransactionConfirmViewController? @IBOutlet weak var valueLabel: UILabel! @IBOutlet weak var gasLabel: UILabel! @@ -217,11 +216,7 @@ class ContractController: UITableViewController { // TODO: set gas price // paramBuilder.gasPrice = gasPrice.words.first! } - let controller: TransactionConfirmViewController = UIStoryboard(name: .transactionHistory).instantiateViewController() - controller.modalPresentationStyle = .overCurrentContext - controller.paramBuilder = paramBuilder - present(controller, animated: false, completion: nil) - confirmViewController = controller + // TODO: password and confirm } } diff --git a/Neuron/Sections/Transaction/TransactionConfirmViewController.swift b/Neuron/Sections/Transaction/TransactionConfirmViewController.swift deleted file mode 100644 index 622843d2..00000000 --- a/Neuron/Sections/Transaction/TransactionConfirmViewController.swift +++ /dev/null @@ -1,199 +0,0 @@ -// -// TransactionConfirmViewController.swift -// Neuron -// -// Created by 晨风 on 2018/10/31. -// Copyright © 2018 Cryptape. All rights reserved. -// - -import UIKit -import IQKeyboardManagerSwift - -protocol TransactionConfirmViewControllerDelegate: NSObjectProtocol { - func transactionConfirmWalletPassword(_ controller: TransactionConfirmViewController, password: String) - func transactionCanceled(_ controller: TransactionConfirmViewController) -} - -class TransactionConfirmViewController: UIViewController, TransactionConfirmSendViewControllerDelegate, TransactionConfirmInfoViewControllerDelegate { - weak var delegate: TransactionConfirmViewControllerDelegate? - @IBOutlet weak var containTopConstraint: NSLayoutConstraint! - @IBOutlet weak var backgroundView: UIView! - @IBOutlet weak var contentView: UIView! - @IBOutlet weak var titleLabel: UILabel! - @IBOutlet weak var containView: UIView! - var paramBuilder: TransactionParamBuilder! { - didSet { - let controller: TransactionConfirmInfoViewController = UIStoryboard(name: .transactionHistory).instantiateViewController() - controller.paramBuilder = paramBuilder - controller.delegate = self - contentViewController = controller - } - } - var contentViewController: UIViewController? { - didSet { - guard let controller = contentViewController else { return } - _ = view // load view - titleLabel.text = controller.title - controller.view.frame = CGRect( - x: 0, - y: containView.bounds.height - controller.preferredContentSize.height, - width: containView.bounds.size.width, - height: controller.preferredContentSize.height - ) - containView.addSubview(controller.view) - - if let oldValue = oldValue { - let offset = oldValue.view.bounds.size.width - controller.view.transform = CGAffineTransform(translationX: offset, y: 0) - containView.addSubview(controller.view) - UIView.animate(withDuration: CATransaction.animationDuration(), animations: { - oldValue.view.transform = CGAffineTransform(translationX: -offset, y: 0) - controller.view.transform = CGAffineTransform.identity - }, completion: { (_) in - oldValue.view.removeFromSuperview() - }) - } - } - } - - override func viewDidLoad() { - super.viewDidLoad() - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - backgroundView.alpha = 0.0 - contentView.transform = CGAffineTransform(translationX: 0, y: contentView.bounds.size.height) - UIView.animate(withDuration: CATransaction.animationDuration()) { - self.backgroundView.alpha = 1.0 - self.contentView.transform = CGAffineTransform.identity - } - IQKeyboardManager.shared.enable = false - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - IQKeyboardManager.shared.enable = true - } - - @IBAction func dismiss() { - UIView.animate(withDuration: CATransaction.animationDuration(), animations: { - self.backgroundView.alpha = 0.0 - self.contentView.transform = CGAffineTransform(translationX: 0, y: self.contentView.bounds.size.height) - }, completion: { (_) in - self.dismiss(animated: false, completion: nil) - self.delegate?.transactionCanceled(self) - }) - } - - func confirmWalletPassword(password: String) { - if paramBuilder != nil { - sendTransaction() - } else { - delegate?.transactionConfirmWalletPassword(self, password: password) - } - } - - func confirmTransactionInfo() { - let controller: TransactionConfirmSendViewController = UIStoryboard(name: .transactionHistory).instantiateViewController() - controller.delegate = self - contentViewController = controller - } - - @objc func keyboardWillShow(notification: Notification) { - let boundsValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue - guard let bounds = boundsValue?.cgRectValue else { return } - let durationValue = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber - guard let duration = durationValue?.doubleValue else { return } - let curveValue = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber - guard let curve = curveValue?.intValue else { return } - let offset = view.bounds.size.height - contentView.bounds.size.height - bounds.size.height - contentView.frame.origin.y - if duration > 0 { - let options = UIView.AnimationOptions(rawValue: UInt(curve << 16)) - UIView.animate(withDuration: duration, delay: 0.0, options: options, animations: { - self.contentView.transform = self.contentView.transform.translatedBy(x: 0, y: offset) - }, completion: { (_) in - }) - } - } - - @objc func keyboardWillHide(notification: Notification) { - let durationValue = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber - guard let duration = durationValue?.doubleValue else { return } - let curveValue = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber - guard let curve = curveValue?.intValue else { return } - let options = UIView.AnimationOptions(rawValue: UInt(curve << 16)) - UIView.animate(withDuration: duration, delay: 0, options: options, animations: { - self.contentView.transform = CGAffineTransform.identity - }, completion: nil) - } -} - -private extension TransactionConfirmViewController { - func sendTransaction() { - Toast.showHUD() - DispatchQueue.global().async { - DispatchQueue.main.async { - // TODO: send tx - //self.paramBuilder.sendTransaction(password: password) - Toast.hideHUD() - } - } - } -} - -protocol TransactionConfirmInfoViewControllerDelegate: NSObjectProtocol { - func confirmTransactionInfo() -} - -class TransactionConfirmInfoViewController: UIViewController { - weak var delegate: TransactionConfirmInfoViewControllerDelegate? - @IBOutlet weak var amountLabel: UILabel! - @IBOutlet weak var fromAddressLabel: UILabel! - @IBOutlet weak var toAddressLabel: UILabel! - @IBOutlet weak var gasCostLabel: UILabel! - var paramBuilder: TransactionParamBuilder! { - didSet { - _ = view // load view - amountLabel.text = Double.fromAmount(paramBuilder.value, decimals: paramBuilder.decimals).decimal - let range = NSMakeRange(amountLabel.text!.lengthOfBytes(using: .utf8), paramBuilder.symbol.lengthOfBytes(using: .utf8)) - amountLabel.text! += paramBuilder.symbol - let attributedText = NSMutableAttributedString(attributedString: amountLabel.attributedText!) - attributedText.addAttributes([NSAttributedString.Key.font: UIFont.systemFont(ofSize: 24)], range: range) - amountLabel.attributedText = attributedText - fromAddressLabel.text = paramBuilder.from - toAddressLabel.text = paramBuilder.to - gasCostLabel.text = "\(paramBuilder.txFeeNatural.decimal)\(paramBuilder.nativeCoinSymbol)" - } - } - - @IBAction func confirm(_ sender: Any) { - delegate?.confirmTransactionInfo() - } - - override func touchesEnded(_ touches: Set, with event: UIEvent?) { - } -} - -protocol TransactionConfirmSendViewControllerDelegate: NSObjectProtocol { - func confirmWalletPassword(password: String) -} - -class TransactionConfirmSendViewController: UIViewController { - weak var delegate: TransactionConfirmSendViewControllerDelegate? - @IBOutlet weak var passwordTextField: UITextField! - - @IBAction func confirm(_ sender: Any) { - let password = passwordTextField.text ?? "" - if case .invalid(let reason) = PasswordValidator.validate(password: password) { - Toast.showToast(text: reason) - return - } - delegate?.confirmWalletPassword(password: password) - } - - override func touchesEnded(_ touches: Set, with event: UIEvent?) { - } -} diff --git a/Neuron/Sections/Transaction/TransactionHistory.storyboard b/Neuron/Sections/Transaction/TransactionHistory.storyboard index 26e641df..a99d26f1 100644 --- a/Neuron/Sections/Transaction/TransactionHistory.storyboard +++ b/Neuron/Sections/Transaction/TransactionHistory.storyboard @@ -10,100 +10,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -378,7 +284,6 @@ - From 3b08cd8b9598ebe51cabfdd4bc07162f2db753af Mon Sep 17 00:00:00 2001 From: James Chen Date: Tue, 20 Nov 2018 16:02:23 +0900 Subject: [PATCH 112/315] Construct AppChain instance with RPC node only when URL is present --- .../Transaction/SendTransactionViewController.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index 8bab324a..0c4c0b51 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -192,12 +192,18 @@ private extension SendTransactionViewController { } func sendAppChainTransaction(password: String) throws -> TxHash { - guard let appChainUrl = URL(string: paramBuilder.rpcNode) else { - throw SendTransactionError.invalidAppChainNode + let appChain: AppChain + if paramBuilder.rpcNode.isEmpty { + appChain = AppChainNetwork.appChain() + } else { + guard let appChainUrl = URL(string: paramBuilder.rpcNode) else { + throw SendTransactionError.invalidAppChainNode + } + appChain = AppChainNetwork.appChain(url: appChainUrl) } if paramBuilder.tokenType == .appChain { let sender = try AppChainTxSender( - appChain: AppChainNetwork.appChain(url: appChainUrl), + appChain: appChain, walletManager: WalletManager.default, from: paramBuilder.from ) From 17137206b0e61d62378b755bdc08aa83477f2810 Mon Sep 17 00:00:00 2001 From: LuFP Date: Tue, 20 Nov 2018 15:44:06 +0800 Subject: [PATCH 113/315] Add DAppViewController navigationBar --- .../search_dapp.imageset/Contents.json | 22 ++++++++++++++++ .../search_dapp.imageset/search_dapp@2x.png | Bin 0 -> 1696 bytes .../search_dapp.imageset/search_dapp@3x.png | Bin 0 -> 2617 bytes Neuron/Base.lproj/Main.storyboard | 24 +++++++++++++++--- Neuron/Constants/UIStyle.swift | 8 ------ .../Sections/Dapp/BrowserViewController.swift | 6 ++++- Neuron/Sections/Dapp/DappViewController.swift | 12 ++++----- 7 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 Neuron/Assets.xcassets/NavigationBar/search_dapp.imageset/Contents.json create mode 100644 Neuron/Assets.xcassets/NavigationBar/search_dapp.imageset/search_dapp@2x.png create mode 100644 Neuron/Assets.xcassets/NavigationBar/search_dapp.imageset/search_dapp@3x.png diff --git a/Neuron/Assets.xcassets/NavigationBar/search_dapp.imageset/Contents.json b/Neuron/Assets.xcassets/NavigationBar/search_dapp.imageset/Contents.json new file mode 100644 index 00000000..5f44fdf6 --- /dev/null +++ b/Neuron/Assets.xcassets/NavigationBar/search_dapp.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "search_dapp@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "search_dapp@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/NavigationBar/search_dapp.imageset/search_dapp@2x.png b/Neuron/Assets.xcassets/NavigationBar/search_dapp.imageset/search_dapp@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..cb710e9bb8854382fd01fe5a5904ab6a6849f856 GIT binary patch literal 1696 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 - + - + @@ -87,19 +87,34 @@ - + + + + + + + + + + + + + + + + - + @@ -116,6 +131,7 @@ + diff --git a/Neuron/Constants/UIStyle.swift b/Neuron/Constants/UIStyle.swift index f27f57ce..7b7ae177 100644 --- a/Neuron/Constants/UIStyle.swift +++ b/Neuron/Constants/UIStyle.swift @@ -24,11 +24,3 @@ struct StatusBar { static let statusBarHeight = UIApplication.shared.statusBarFrame.size.height static let navigationBarHeight: CGFloat = 44.0 } - -//is bangs screen -func isBangsScreen() -> Bool { - guard #available(iOS 11.0, *) else { - return false - } - return UIApplication.shared.windows[0].safeAreaInsets.bottom > 0.0 -} diff --git a/Neuron/Sections/Dapp/BrowserViewController.swift b/Neuron/Sections/Dapp/BrowserViewController.swift index d21b4abb..cfd4e25d 100644 --- a/Neuron/Sections/Dapp/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/BrowserViewController.swift @@ -16,7 +16,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe var webViewProgressObservation: NSKeyValueObservation! lazy var webView: WKWebView = { let webView = WKWebView( - frame: CGRect(x: 0, y: 0, width: ScreenSize.width, height: ScreenSize.height - 64), + frame: .zero, configuration: self.config ) let infoDictionary = Bundle.main.infoDictionary! @@ -46,6 +46,10 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe return config }() + override func viewDidLayoutSubviews() { + self.webView.frame = self.view.bounds + } + override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(false, animated: true) diff --git a/Neuron/Sections/Dapp/DappViewController.swift b/Neuron/Sections/Dapp/DappViewController.swift index 0af91edc..7981e6cf 100644 --- a/Neuron/Sections/Dapp/DappViewController.swift +++ b/Neuron/Sections/Dapp/DappViewController.swift @@ -20,7 +20,7 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - navigationController?.setNavigationBarHidden(true, animated: true) + super.navigationController?.setNavigationBarHidden(false, animated: true) } override func viewDidLoad() { @@ -40,11 +40,7 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, } func didAddSubLayout() { - if isBangsScreen() { - webView = WKWebView(frame: CGRect(x: 0, y: 20, width: ScreenSize.width, height: ScreenSize.height - 49 - 40)) - } else { - webView = WKWebView(frame: CGRect(x: 0, y: 20, width: ScreenSize.width, height: ScreenSize.height - 49 - 20)) - } + webView = WKWebView(frame: .zero) var js = "" if let path = Bundle.main.path(forResource: "dappOpration", ofType: "js") { @@ -73,6 +69,10 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, view.addSubview(webView) } + override func viewDidLayoutSubviews() { + self.webView.frame = self.view.bounds + } + //scrollView代理 func viewForZooming(in scrollView: UIScrollView) -> UIView? { return nil From abab0a399605c71ccd3182054521e63ffc097099 Mon Sep 17 00:00:00 2001 From: James Chen Date: Tue, 20 Nov 2018 17:16:45 +0900 Subject: [PATCH 114/315] Apply new tx flow UI to contract call --- .../Dapp/WebView/ContractController.swift | 124 ++++++++++++----- .../SendTransactionViewController.swift | 128 ++++++++++-------- 2 files changed, 160 insertions(+), 92 deletions(-) diff --git a/Neuron/Sections/Dapp/WebView/ContractController.swift b/Neuron/Sections/Dapp/WebView/ContractController.swift index 083cfc91..82b731c2 100644 --- a/Neuron/Sections/Dapp/WebView/ContractController.swift +++ b/Neuron/Sections/Dapp/WebView/ContractController.swift @@ -7,6 +7,7 @@ // import UIKit +import BLTNBoard import AppChain import BigInt import Web3swift @@ -21,12 +22,17 @@ protocol ContractControllerDelegate: class { func callBackWebView(id: Int, value: String, error: DAppError?) } -class ContractController: UITableViewController { +class ContractController: UITableViewController, TransactonSender { var requestAddress: String = "" var dappName: String = "" var dappCommonModel: DAppCommonModel! + var paramBuilder: TransactionParamBuilder! private var chainType: ChainType = .appChain - private var tokenModel = TokenModel() + private var tokenModel = TokenModel() { + didSet { + paramBuilder = TransactionParamBuilder(token: tokenModel) + } + } var advancedViewController: AdvancedViewController! weak var delegate: ContractControllerDelegate? @@ -43,6 +49,19 @@ class ContractController: UITableViewController { private var ethereumGas: String? private var value: String! // both appChain'amount and Ethereum'amount + private lazy var summaryPageItem: TxSummaryPageItem = { + return TxSummaryPageItem.create() + }() + private lazy var bulletinManager: BLTNItemManager = { + let passwordPageItem = createPasswordPageItem() + summaryPageItem.next = passwordPageItem + summaryPageItem.actionHandler = { item in + item.manager?.displayNextItem() + } + + return BLTNItemManager(rootItem: summaryPageItem) + }() + override func viewDidLoad() { super.viewDidLoad() title = "支付详情" @@ -105,9 +124,23 @@ class ContractController: UITableViewController { formatValueLabel(value: value) } + private func createPasswordPageItem() -> PasswordPageItem { + let passwordPageItem = PasswordPageItem.create() + + passwordPageItem.actionHandler = { [weak self] item in + item.manager?.displayActivityIndicator() + guard let self = self else { + return + } + self.sendTransaction(password: passwordPageItem.passwordField.text!) + } + + return passwordPageItem + } + func formatValueLabel(value: String) { - let range = NSMakeRange(valueLabel.text!.lengthOfBytes(using: .utf8), tokenModel.symbol.lengthOfBytes(using: .utf8)) - valueLabel.text! += tokenModel.symbol + let range = NSRange(location: valueLabel.text!.lengthOfBytes(using: .utf8), length: tokenModel.symbol.lengthOfBytes(using: .utf8) + 1) + valueLabel.text! += " " + tokenModel.symbol let attributedText = NSMutableAttributedString(attributedString: valueLabel.attributedText!) attributedText.addAttributes([NSAttributedString.Key.font: UIFont.systemFont(ofSize: 24)], range: range) valueLabel.attributedText = attributedText @@ -197,50 +230,77 @@ class ContractController: UITableViewController { } @IBAction func didClickConfirmButton(_ sender: UIButton) { - let paramBuilder = TransactionParamBuilder(token: tokenModel) paramBuilder.from = WalletRealmTool.getCurrentAppModel().currentWallet!.address - // TODO: set input amount - // paramBuilder.amount = Double(value) ?? 0.0 + paramBuilder.value = Double(value)!.toAmount(tokenModel.decimals) switch chainType { case .appChain: paramBuilder.gasLimit = 10000000 + paramBuilder.gasPrice = BigUInt(dappCommonModel.appChain?.quota.trailingZerosTrimmed ?? "") ?? 1000000 paramBuilder.to = dappCommonModel.appChain?.to ?? "" paramBuilder.data = Data(hex: dappCommonModel.appChain?.data ?? "") - // TODO: set gas price - // paramBuilder.gasPrice = BigUInt(dappCommonModel.appChain?.quota.clean ?? "")?.words.first ?? 1000000 case .eth: - paramBuilder.gasLimit = UInt64(gasLimit.words.first!) + paramBuilder.gasLimit = UInt64(gasLimit) + paramBuilder.gasPrice = gasPrice paramBuilder.to = dappCommonModel.eth?.to ?? "" paramBuilder.data = Data(hex: dappCommonModel.eth?.data ?? "") - // TODO: set gas price - // paramBuilder.gasPrice = gasPrice.words.first! } - // TODO: password and confirm + + summaryPageItem.update(paramBuilder) + bulletinManager.showBulletin(above: self) } } -// TODO: tx sent -extension ContractController { - /* - func transactionCompletion(_ transactionService: TransactionParamBuilder, result: TransactionParamBuilder.Result) { - switch result { - case .error: - delegate?.callBackWebView(id: dappCommonModel.id, value: "", error: DAppError.sendTransactionFailed) - case .succee(let txhash): - SensorsAnalytics.Track.transaction( - chainType: tokenModel.chainId, - currencyType: tokenModel.symbol, - currencyNumber: Double(value) ?? 0.0, - receiveAddress: dappCommonModel.appChain?.to ?? "", - outcomeAddress: WalletRealmTool.getCurrentAppModel().currentWallet!.address, - transactionType: .normal - ) +private extension ContractController { + func sendTransaction(password: String) { + DispatchQueue.global().async { + do { + let txHash: TxHash + if self.paramBuilder.tokenType == .ether || self.paramBuilder.tokenType == .erc20 { + txHash = try self.sendEthereumTransaction(password: password) + } else { + txHash = try self.sendAppChainTransaction(password: password) + } + + DispatchQueue.main.async { + let successPageItem = SuccessPageItem.create(title: "交易已发送") + successPageItem.actionHandler = { item in + self.transactionDidSend(txhash: txHash) + } + self.bulletinManager.push(item: successPageItem) + } + } catch let error { + DispatchQueue.main.async { + self.bulletinManager.hideActivityIndicator() + let passwordPageItem = self.createPasswordPageItem() + self.bulletinManager.push(item: passwordPageItem) + passwordPageItem.errorMessage = error.localizedDescription + } + } + } + } + + func transactionDidSend(txhash: TxHash?) { + if let txhash = txhash { delegate?.callBackWebView(id: dappCommonModel.id, value: txhash.addHexPrefix(), error: nil) + track() + bulletinManager.dismissBulletin() + navigationController?.popViewController(animated: true) + } else { + delegate?.callBackWebView(id: dappCommonModel.id, value: "", error: DAppError.sendTransactionFailed) } - confirmViewController?.dismiss() - navigationController?.popViewController(animated: true) - }*/ + } + + func track() { + SensorsAnalytics.Track.transaction( + chainType: tokenModel.chainId, + currencyType: tokenModel.symbol, + currencyNumber: Double(value) ?? 0.0, + receiveAddress: dappCommonModel.appChain?.to ?? "", + outcomeAddress: WalletRealmTool.getCurrentAppModel().currentWallet!.address, + transactionType: .normal + ) + } } extension ContractController: AdvancedViewControllerDelegate { diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index 0c4c0b51..c324ada0 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -13,7 +13,73 @@ import AppChain import Web3swift import EthereumAddress -class SendTransactionViewController: UITableViewController { +protocol TransactonSender { + var paramBuilder: TransactionParamBuilder! { get set } + func sendEthereumTransaction(password: String) throws -> TxHash + func sendAppChainTransaction(password: String) throws -> TxHash +} + +extension TransactonSender { + func sendEthereumTransaction(password: String) throws -> TxHash { + let keystore = WalletManager.default.keystore(for: paramBuilder.from) + let web3 = EthereumNetwork().getWeb3() + web3.addKeystoreManager(KeystoreManager([keystore])) + + if paramBuilder.tokenType == .ether { + let sender = try EthereumTxSender(web3: web3, from: paramBuilder.from) + return try sender.sendETH( + to: paramBuilder.to, + value: paramBuilder.value, + gasLimit: paramBuilder.gasLimit, + gasPrice: BigUInt(paramBuilder.gasPrice), + data: paramBuilder.data, + password: password + ) + } else { + let sender = try EthereumTxSender(web3: web3, from: paramBuilder.from) + // TODO: estimate gas + return try sender.sendToken( + to: paramBuilder.to, + value: paramBuilder.value, + gasLimit: paramBuilder.gasLimit, + gasPrice: BigUInt(paramBuilder.gasPrice), + contractAddress: paramBuilder.contractAddress, + password: password + ) + } + } + + func sendAppChainTransaction(password: String) throws -> TxHash { + let appChain: AppChain + if paramBuilder.rpcNode.isEmpty { + appChain = AppChainNetwork.appChain() + } else { + guard let appChainUrl = URL(string: paramBuilder.rpcNode) else { + throw SendTransactionError.invalidAppChainNode + } + appChain = AppChainNetwork.appChain(url: appChainUrl) + } + if paramBuilder.tokenType == .appChain { + let sender = try AppChainTxSender( + appChain: appChain, + walletManager: WalletManager.default, + from: paramBuilder.from + ) + return try sender.send( + to: paramBuilder.to, + value: paramBuilder.value, + quota: paramBuilder.gasLimit, + data: paramBuilder.data, + chainId: BigUInt(paramBuilder.chainId)!, + password: password + ) + } else { + return "" // TODO: AppChainErc20 not implemented yet. + } + } +} + +class SendTransactionViewController: UITableViewController, TransactonSender { @IBOutlet private weak var walletIconView: UIImageView! @IBOutlet private weak var walletNameLabel: UILabel! @IBOutlet private weak var walletAddressLabel: UILabel! @@ -22,7 +88,7 @@ class SendTransactionViewController: UITableViewController { @IBOutlet private weak var gasCostLabel: UILabel! @IBOutlet private weak var addressTextField: UITextField! - private var paramBuilder: TransactionParamBuilder! + var paramBuilder: TransactionParamBuilder! private var observers = [NSKeyValueObservation]() private lazy var summaryPageItem: TxSummaryPageItem = { @@ -162,64 +228,6 @@ private extension SendTransactionViewController { } } - func sendEthereumTransaction(password: String) throws -> TxHash { - let keystore = WalletManager.default.keystore(for: paramBuilder.from) - let web3 = EthereumNetwork().getWeb3() - web3.addKeystoreManager(KeystoreManager([keystore])) - - if paramBuilder.tokenType == .ether { - let sender = try EthereumTxSender(web3: web3, from: paramBuilder.from) - return try sender.sendETH( - to: paramBuilder.to, - value: paramBuilder.value, - gasLimit: paramBuilder.gasLimit, - gasPrice: BigUInt(paramBuilder.gasPrice), - data: paramBuilder.data, - password: password - ) - } else { - let sender = try EthereumTxSender(web3: web3, from: paramBuilder.from) - // TODO: estimate gas - return try sender.sendToken( - to: paramBuilder.to, - value: paramBuilder.value, - gasLimit: paramBuilder.gasLimit, - gasPrice: BigUInt(paramBuilder.gasPrice), - contractAddress: paramBuilder.contractAddress, - password: password - ) - } - } - - func sendAppChainTransaction(password: String) throws -> TxHash { - let appChain: AppChain - if paramBuilder.rpcNode.isEmpty { - appChain = AppChainNetwork.appChain() - } else { - guard let appChainUrl = URL(string: paramBuilder.rpcNode) else { - throw SendTransactionError.invalidAppChainNode - } - appChain = AppChainNetwork.appChain(url: appChainUrl) - } - if paramBuilder.tokenType == .appChain { - let sender = try AppChainTxSender( - appChain: appChain, - walletManager: WalletManager.default, - from: paramBuilder.from - ) - return try sender.send( - to: paramBuilder.to, - value: paramBuilder.value, - quota: paramBuilder.gasLimit, - data: paramBuilder.data, - chainId: BigUInt(paramBuilder.chainId)!, - password: password - ) - } else { - return "" // TODO: AppChainErc20 not implemented yet. - } - } - var isEffectiveTransferInfo: Bool { guard Address.isValid(paramBuilder.to) && paramBuilder.to != "0x" else { Toast.showToast(text: "您的地址错误,请重新输入") From 9a26a5d3faf85539a0eeb15ff136cffcf7107d35 Mon Sep 17 00:00:00 2001 From: James Chen Date: Tue, 20 Nov 2018 17:27:41 +0900 Subject: [PATCH 115/315] Find first transaction instead of enumerating over the whole list --- .../TransactionHistoryPresenter.swift | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift b/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift index 2e120f62..85ad2ed1 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift @@ -32,7 +32,7 @@ class TransactionHistoryPresenter: NSObject, TransactionStatusManagerDelegate { func reloadData(completion: CallbackBlock? = nil) { guard !loading else { - return + return } page = 1 hasMoreData = true @@ -137,15 +137,13 @@ class TransactionHistoryPresenter: NSObject, TransactionStatusManagerDelegate { self.delegate?.didLoadTransactions(transaction: self.transactions, insertions: [0], error: nil) } } + func sentTransactionStatusChanged(transaction: TransactionDetails) { - DispatchQueue.main.async { - for (idx, trans) in self.transactions.enumerated() { - if trans.hash == transaction.hash { - self.transactions.remove(at: idx) - self.transactions.insert(transaction, at: idx) - self.delegate?.updateTransactions(transaction: self.transactions, updates: [idx], error: nil) - return - } + if let idx = transactions.firstIndex(where: { $0.hash == transaction.hash }) { + DispatchQueue.main.async { + self.transactions.remove(at: idx) + self.transactions.insert(transaction, at: idx) + self.delegate?.updateTransactions(transaction: self.transactions, updates: [idx], error: nil) } } } From 9730b1232d67c586794f07fa650d8c4b30e9da23 Mon Sep 17 00:00:00 2001 From: LuFP Date: Tue, 20 Nov 2018 17:10:42 +0800 Subject: [PATCH 116/315] Add webview direction image --- .../webview_ahead_off.imageset/Contents.json | 22 +++++++++ .../webview_ahead_off@2x.png | Bin 0 -> 743 bytes .../webview_ahead_off@3x.png | Bin 0 -> 743 bytes .../webview_ahead_on.imageset/Contents.json | 22 +++++++++ .../webview_ahead_on@2x.png | Bin 0 -> 744 bytes .../webview_ahead_on@3x.png | Bin 0 -> 744 bytes .../webview_back_off.imageset/Contents.json | 22 +++++++++ .../webview_back_off@2x.png | Bin 0 -> 760 bytes .../webview_back_off@3x.png | Bin 0 -> 760 bytes .../webview_back_on.imageset/Contents.json | 22 +++++++++ .../webview_back_on@2x.png | Bin 0 -> 729 bytes .../webview_back_on@3x.png | Bin 0 -> 729 bytes Neuron/Sections/Dapp/DAppBrowser.storyboard | 42 ++++++++++++++++++ 13 files changed, 130 insertions(+) create mode 100644 Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/Contents.json create mode 100644 Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/webview_ahead_off@2x.png create mode 100644 Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/webview_ahead_off@3x.png create mode 100644 Neuron/Assets.xcassets/TabBar/webview_ahead_on.imageset/Contents.json create mode 100644 Neuron/Assets.xcassets/TabBar/webview_ahead_on.imageset/webview_ahead_on@2x.png create mode 100644 Neuron/Assets.xcassets/TabBar/webview_ahead_on.imageset/webview_ahead_on@3x.png create mode 100644 Neuron/Assets.xcassets/TabBar/webview_back_off.imageset/Contents.json create mode 100644 Neuron/Assets.xcassets/TabBar/webview_back_off.imageset/webview_back_off@2x.png create mode 100644 Neuron/Assets.xcassets/TabBar/webview_back_off.imageset/webview_back_off@3x.png create mode 100644 Neuron/Assets.xcassets/TabBar/webview_back_on.imageset/Contents.json create mode 100644 Neuron/Assets.xcassets/TabBar/webview_back_on.imageset/webview_back_on@2x.png create mode 100644 Neuron/Assets.xcassets/TabBar/webview_back_on.imageset/webview_back_on@3x.png diff --git a/Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/Contents.json b/Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/Contents.json new file mode 100644 index 00000000..133fdf5e --- /dev/null +++ b/Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "webview_ahead_off@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "webview_ahead_off@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/webview_ahead_off@2x.png b/Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/webview_ahead_off@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a9f359cb35e561bc51dc95c5f61bebe9425f5239 GIT binary patch literal 743 zcmV?P)Px%p-DtRR7ee_)IEq3K@4?nb5``Mk_Rqtd#AIJz2@M#~4@X#{ zphLt?Xu{}kI2Xhu8VfXHfEV0!;;v4n$DW`O6L`r@*|Qo6j*(ypcG*qaH-kv}hi zbFO3Oq#M;3_8tGZ`f&yXs>6-7U*A7v2gT$hR?P)Px%p-DtRR7ee_)IEq3K@4?nb5``Mk_Rqtd#AIJz2@M#~4@X#{ zphLt?Xu{}kI2Xhu8VfXHfEV0!;;v4n$DW`O6L`r@*|Qo6j*(ypcG*qaH-kv}hi zbFO3Oq#M;3_8tGZ`f&yXs>6-7U*A7v2gT$hRP)Px%qDe$SR7ee_)mv{8K@Kx|`-X)I61sI@fl3;5)VPa0b< z@sg zV>5GSxj$+%VzJo?W82tEp~R(NI9j)>rLUu)QjYl^5s4E*2q{Ud#b=V|M*)pMI8qWs z#|4QAKoZxJnDZQ|QLmP2e$#q`=uVLk`$t6$POn}o*Mot{w?Y^|Y2PSl17y^;E4Dvu zy+``k;n8`wZ=m8u!A(n8V#R)5?r+66Ld+ofOr&{PJc zw1D)Rs2Q?;*Q%v;zNc5yf_!tmTK*XdS|62^KxsFoPzZxdswc>3tyZoCL(xxyR0`NG zMhDetUyxC|x>YuV)@PD3h2hRY=s=KBL|O`lBVVvcDWu;Q9SUR=hsIXGoLWc9DJ<3w zOli{}8e|kZ>W3MQe8b7Kh#j?Ehq?k8MWo*YW^@CEzldQMidW*TB@)SFNc#1^y-!Lh zhZJ3q-Z&yhI-So7B~i6rzys4q=W!mlMW+y5aU;9x8f1)we1+BK!-G>*cyCL^d}fWu zt`?;eV*uiEA)j4o^zk!vggvk;z@God`08sN^y=#{#&89TsbXI*c58wwX^ayTSNg89z98rgd-fBqCb@cA5Kb zn@miL33_c|Q0000P)Px%qDe$SR7ee_)mv{8K@Kx|`-X)I61sI@fl3;5)VPa0b< z@sg zV>5GSxj$+%VzJo?W82tEp~R(NI9j)>rLUu)QjYl^5s4E*2q{Ud#b=V|M*)pMI8qWs z#|4QAKoZxJnDZQ|QLmP2e$#q`=uVLk`$t6$POn}o*Mot{w?Y^|Y2PSl17y^;E4Dvu zy+``k;n8`wZ=m8u!A(n8V#R)5?r+66Ld+ofOr&{PJc zw1D)Rs2Q?;*Q%v;zNc5yf_!tmTK*XdS|62^KxsFoPzZxdswc>3tyZoCL(xxyR0`NG zMhDetUyxC|x>YuV)@PD3h2hRY=s=KBL|O`lBVVvcDWu;Q9SUR=hsIXGoLWc9DJ<3w zOli{}8e|kZ>W3MQe8b7Kh#j?Ehq?k8MWo*YW^@CEzldQMidW*TB@)SFNc#1^y-!Lh zhZJ3q-Z&yhI-So7B~i6rzys4q=W!mlMW+y5aU;9x8f1)we1+BK!-G>*cyCL^d}fWu zt`?;eV*uiEA)j4o^zk!vggvk;z@God`08sN^y=#{#&89TsbXI*c58wwX^ayTSNg89z98rgd-fBqCb@cA5Kb zn@miL33_c|Q0000Px%vPnciR7ee_mQ9G$KorO4P3uwIU3CRdURDr6@a9Flh#!}V9|)SQ-WJ?`Y;9RZ zSG3{>D%&QlNks%vOah`SdRdS?c=NJ)5O0bg9u#!(>!Lz4&1EJt|M#2s z&zngCWWZ};;^wjqo0cE_vGnG4xm;#22oC{tcyz2z2xJK3)o)90r;`H!VQBT3+JXrP zh2rq4^?%|5K{Koh0OZ|-IR8lqUyNm0GwfLaQfh;7a%d{6%u{zTWJfjZt3fbrR1}Izz3+o9)*lC1aiUJD+Y7Ofo5V#T*))lDf_B2p(Ee@;$s5a~(mc0@eCgR*} zqv~b>Z)OL9xj?mH=J244%>Gq9VAHyb_2?wAnt3dMhNu>dbN-!hcCe65FZ75DoT7+f z2;p7@vYGcjN}m=oijKG}200=Stq&1O=c{zQNn);2H@A3o{(fMFQixx; zN&G0`$6OYQf5O-QoL=tC!CG|&_wJE<>De!+^0Hv qU0}a|9hTz*wfL`iMAL4q5s<$g8#CIoja1qI0000Px%vPnciR7ee_mQ9G$KorO4P3uwIU3CRdURDr6@a9Flh#!}V9|)SQ-WJ?`Y;9RZ zSG3{>D%&QlNks%vOah`SdRdS?c=NJ)5O0bg9u#!(>!Lz4&1EJt|M#2s z&zngCWWZ};;^wjqo0cE_vGnG4xm;#22oC{tcyz2z2xJK3)o)90r;`H!VQBT3+JXrP zh2rq4^?%|5K{Koh0OZ|-IR8lqUyNm0GwfLaQfh;7a%d{6%u{zTWJfjZt3fbrR1}Izz3+o9)*lC1aiUJD+Y7Ofo5V#T*))lDf_B2p(Ee@;$s5a~(mc0@eCgR*} zqv~b>Z)OL9xj?mH=J244%>Gq9VAHyb_2?wAnt3dMhNu>dbN-!hcCe65FZ75DoT7+f z2;p7@vYGcjN}m=oijKG}200=Stq&1O=c{zQNn);2H@A3o{(fMFQixx; zN&G0`$6OYQf5O-QoL=tC!CG|&_wJE<>De!+^0Hv qU0}a|9hTz*wfL`iMAL4q5s<$g8#CIoja1qI00005P)Px%lSxEDR7ee_)V*)hKo|z_yGxPULIDA`lPXEsv71B;EOF+P1~DKZu`sc8pyES- z3J8Swrc_j7S~8TqjXgwjVw!CVv`9Rj1%xzaKs&9@ECZ;!NpMEgRGytzc^2F3~h z#W}CV0+DPH*y$jHzzFw_t$nJlLqxkc^-fJqwJ4!w z2n;V_6#MRBMVtEQI`#FaYC=qKZR@>~%5I>9jNVr~LB;}-{Xqj5e zX?zWfwm}(+s*j=Y6)ly0i=_21=zpLTC1f1$x1y=(H;&V=2BBS`48_y3V>6qKTrZbb*oCe?HmNt?aUo?w7#$;=O=^IHm)-^Q6pnts)?8{cGTUr5P)Px%lSxEDR7ee_)V*)hKo|z_yGxPULIDA`lPXEsv71B;EOF+P1~DKZu`sc8pyES- z3J8Swrc_j7S~8TqjXgwjVw!CVv`9Rj1%xzaKs&9@ECZ;!NpMEgRGytzc^2F3~h z#W}CV0+DPH*y$jHzzFw_t$nJlLqxkc^-fJqwJ4!w z2n;V_6#MRBMVtEQI`#FaYC=qKZR@>~%5I>9jNVr~LB;}-{Xqj5e zX?zWfwm}(+s*j=Y6)ly0i=_21=zpLTC1f1$x1y=(H;&V=2BBS`48_y3V>6qKTrZbb*oCe?HmNt?aUo?w7#$;=O=^IHm)-^Q6pnts)?8{cGTUr + + + + + + + + + + + + + + + + + + + + + + + + @@ -799,6 +837,10 @@ + + + + From bff0f5200dab8737e76c44fb5bab7cc347fed5b1 Mon Sep 17 00:00:00 2001 From: cezres Date: Tue, 20 Nov 2018 17:12:54 +0800 Subject: [PATCH 117/315] Add icon assets --- .../icon_refresh.imageset/Contents.json | 21 ++++++++++++++++++ .../\345\210\267\346\226\260.png" | Bin 0 -> 1435 bytes .../icon_wallet_add.imageset/Contents.json | 21 ++++++++++++++++++ .../icon_wallet_add.imageset/Group 3.png | Bin 0 -> 1568 bytes .../icon_wallet_qrcode.imageset/Contents.json | 21 ++++++++++++++++++ .../icon_wallet_qrcode.imageset/Group 6.png | Bin 0 -> 951 bytes .../icon_wallet_switch.imageset/Contents.json | 21 ++++++++++++++++++ .../\345\210\207\346\215\242 (2).png" | Bin 0 -> 1626 bytes .../icon_wallet_trans.imageset/Contents.json | 21 ++++++++++++++++++ .../\345\275\242\347\212\266.png" | Bin 0 -> 1457 bytes 10 files changed, 105 insertions(+) create mode 100644 Neuron/Assets.xcassets/Wallet/icon_refresh.imageset/Contents.json create mode 100644 "Neuron/Assets.xcassets/Wallet/icon_refresh.imageset/\345\210\267\346\226\260.png" create mode 100644 Neuron/Assets.xcassets/Wallet/icon_wallet_add.imageset/Contents.json create mode 100644 Neuron/Assets.xcassets/Wallet/icon_wallet_add.imageset/Group 3.png create mode 100644 Neuron/Assets.xcassets/Wallet/icon_wallet_qrcode.imageset/Contents.json create mode 100644 Neuron/Assets.xcassets/Wallet/icon_wallet_qrcode.imageset/Group 6.png create mode 100644 Neuron/Assets.xcassets/Wallet/icon_wallet_switch.imageset/Contents.json create mode 100644 "Neuron/Assets.xcassets/Wallet/icon_wallet_switch.imageset/\345\210\207\346\215\242 (2).png" create mode 100644 Neuron/Assets.xcassets/Wallet/icon_wallet_trans.imageset/Contents.json create mode 100644 "Neuron/Assets.xcassets/Wallet/icon_wallet_trans.imageset/\345\275\242\347\212\266.png" diff --git a/Neuron/Assets.xcassets/Wallet/icon_refresh.imageset/Contents.json b/Neuron/Assets.xcassets/Wallet/icon_refresh.imageset/Contents.json new file mode 100644 index 00000000..5e2f928b --- /dev/null +++ b/Neuron/Assets.xcassets/Wallet/icon_refresh.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "刷新.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git "a/Neuron/Assets.xcassets/Wallet/icon_refresh.imageset/\345\210\267\346\226\260.png" "b/Neuron/Assets.xcassets/Wallet/icon_refresh.imageset/\345\210\267\346\226\260.png" new file mode 100644 index 0000000000000000000000000000000000000000..15e2d2e9f10d5661fe5d6226e39b6f9eef1cf36a GIT binary patch literal 1435 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 Z=NHC{yLsm-A}9a=002ovPDHLkV1i#&!Hxg` literal 0 HcmV?d00001 diff --git a/Neuron/Assets.xcassets/Wallet/icon_wallet_switch.imageset/Contents.json b/Neuron/Assets.xcassets/Wallet/icon_wallet_switch.imageset/Contents.json new file mode 100644 index 00000000..89200884 --- /dev/null +++ b/Neuron/Assets.xcassets/Wallet/icon_wallet_switch.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "切换 (2).png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git "a/Neuron/Assets.xcassets/Wallet/icon_wallet_switch.imageset/\345\210\207\346\215\242 (2).png" "b/Neuron/Assets.xcassets/Wallet/icon_wallet_switch.imageset/\345\210\207\346\215\242 (2).png" new file mode 100644 index 0000000000000000000000000000000000000000..dca858ccb4f6fc7b7255b119f6b205ef0f3beb6e GIT binary patch literal 1626 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 LNkvXXu0mjf9YeTI literal 0 HcmV?d00001 From 11ce7066fe9809e5b0f2f9ff0cb2b8fe2c09553e Mon Sep 17 00:00:00 2001 From: cezres Date: Tue, 20 Nov 2018 18:07:09 +0800 Subject: [PATCH 118/315] Modify wallet page --- Neuron/Sections/Common/DesignableButton.swift | 24 + .../Common/DesignableGradientView.swift | 67 ++ .../Wallet/Home/TokensViewController.swift | 20 +- Neuron/Sections/Wallet/Home/Wallet.storyboard | 660 +++++++++++++++++- .../Wallet/Home/WalletPresenter.swift | 148 ++++ .../Wallet/Home/WalletViewController.swift | 276 +++----- .../TableViewCell/TokenTableViewCell.swift | 39 +- Neuron/Services/Common/Token.swift | 72 ++ 8 files changed, 1091 insertions(+), 215 deletions(-) create mode 100644 Neuron/Sections/Common/DesignableButton.swift create mode 100644 Neuron/Sections/Common/DesignableGradientView.swift create mode 100644 Neuron/Sections/Wallet/Home/WalletPresenter.swift create mode 100644 Neuron/Services/Common/Token.swift diff --git a/Neuron/Sections/Common/DesignableButton.swift b/Neuron/Sections/Common/DesignableButton.swift new file mode 100644 index 00000000..584a64db --- /dev/null +++ b/Neuron/Sections/Common/DesignableButton.swift @@ -0,0 +1,24 @@ +// +// DesignableButton.swift +// Neuron +// +// Created by 晨风 on 2018/11/20. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit + +@IBDesignable class DesignableButton: UIButton { + @IBInspectable var imageFrame: CGRect = .zero + @IBInspectable var titleFrame: CGRect = .zero + + override func layoutSubviews() { + super.layoutSubviews() + if imageFrame != .zero { + imageView?.frame = imageFrame + } + if titleFrame != .zero { + titleLabel?.frame = titleFrame + } + } +} diff --git a/Neuron/Sections/Common/DesignableGradientView.swift b/Neuron/Sections/Common/DesignableGradientView.swift new file mode 100644 index 00000000..d7c331e0 --- /dev/null +++ b/Neuron/Sections/Common/DesignableGradientView.swift @@ -0,0 +1,67 @@ +// +// DesignableGradientView.swift +// Neuron +// +// Created by 晨风 on 2018/11/20. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit + +@IBDesignable class DesignableGradientView: UIView { + lazy var gradientLayer: CAGradientLayer = { + let layer = CAGradientLayer() + self.layer.insertSublayer(layer, at: 0) + return layer + }() + + override func layoutSubviews() { + super.layoutSubviews() + gradientLayer.frame = bounds + } + + @IBInspectable var startPoint: CGPoint = .zero { + didSet { + gradientLayer.startPoint = startPoint + } + } + + @IBInspectable var endPoint: CGPoint = .zero { + didSet { + gradientLayer.endPoint = endPoint + } + } + + @IBInspectable var color1: UIColor? { + didSet { + gradientLayer.colors = colors + } + } + + @IBInspectable var color2: UIColor? { + didSet { + gradientLayer.colors = colors + } + } + + @IBInspectable var color3: UIColor? { + didSet { + gradientLayer.colors = colors + } + } + + var colors: [CGColor] { + var colors = [CGColor]() + if let color = color1 { + colors.append(color.cgColor) + } + if let color = color2 { + colors.append(color.cgColor) + } + if let color = color3 { + colors.append(color.cgColor) + } + return colors + } +} + diff --git a/Neuron/Sections/Wallet/Home/TokensViewController.swift b/Neuron/Sections/Wallet/Home/TokensViewController.swift index b74ebdae..c62eade4 100644 --- a/Neuron/Sections/Wallet/Home/TokensViewController.swift +++ b/Neuron/Sections/Wallet/Home/TokensViewController.swift @@ -169,16 +169,16 @@ class TokensViewController: UITableViewController, ErrorOverlayPresentable { override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "tokenTableviewcell") as! TokenTableViewCell - let model = tokenArray[indexPath.row] - cell.tokenImage.sd_setImage(with: URL(string: model.iconUrl!), placeholderImage: UIImage(named: "eth_logo")) - cell.balance.text = model.tokenBalance - cell.token.text = model.symbol - cell.network.text = (model.chainName?.isEmpty)! ? "Ethereum Mainnet": model.chainName - if model.currencyAmount.count != 0 { - cell.currency.text = currentCurrencyModel.symbol + model.currencyAmount - } else { - cell.currency.text = "" - } +// let model = tokenArray[indexPath.row] +// cell.tokenImage.sd_setImage(with: URL(string: model.iconUrl!), placeholderImage: UIImage(named: "eth_logo")) +// cell.balance.text = model.tokenBalance +// cell.token.text = model.symbol +// cell.network.text = (model.chainName?.isEmpty)! ? "Ethereum Mainnet": model.chainName +// if model.currencyAmount.count != 0 { +// cell.currency.text = currentCurrencyModel.symbol + model.currencyAmount +// } else { +// cell.currency.text = "" +// } return cell } diff --git a/Neuron/Sections/Wallet/Home/Wallet.storyboard b/Neuron/Sections/Wallet/Home/Wallet.storyboard index e22b038e..b4fec93a 100644 --- a/Neuron/Sections/Wallet/Home/Wallet.storyboard +++ b/Neuron/Sections/Wallet/Home/Wallet.storyboard @@ -1,5 +1,5 @@ - + @@ -10,10 +10,10 @@ - + - + @@ -206,7 +206,7 @@ - + @@ -231,7 +231,7 @@ - + @@ -332,9 +332,7 @@ - - - + @@ -356,7 +354,7 @@ - + @@ -435,7 +433,7 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -566,13 +1196,19 @@ + + + + + + - + diff --git a/Neuron/Sections/Wallet/Home/WalletPresenter.swift b/Neuron/Sections/Wallet/Home/WalletPresenter.swift new file mode 100644 index 00000000..1f5bbea7 --- /dev/null +++ b/Neuron/Sections/Wallet/Home/WalletPresenter.swift @@ -0,0 +1,148 @@ +// +// WalletPresenter.swift +// Neuron +// +// Created by 晨风 on 2018/11/20. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import RealmSwift +import BigInt +import PromiseKit +import Web3swift +import EthereumAddress + +protocol WalletPresenterDelegate: NSObjectProtocol { + func walletPresenterBeganRefresh(presenter: WalletPresenter) + func walletPresenterEndedRefresh(presenter: WalletPresenter) + func walletPresenter(presenter: WalletPresenter, didRefreshToken tokens: [Token]) + func walletPresenter(presenter: WalletPresenter, didRefreshTotalAmount amount: Double) + func walletPresenter(presenter: WalletPresenter, didRefreshCurrency currency: LocalCurrency) + func walletPresenter(presenter: WalletPresenter, didRefreshTokenAmount token: Token) +} + +class WalletPresenter { + var currentWallet: WalletModel? + var currency: LocalCurrency! + private (set) var tokens = [Token]() + weak var delegate: WalletPresenterDelegate? + + private var notificationToken: NotificationToken? + private(set) var refreshing = false + + init() { + notificationToken = WalletRealmTool.getCurrentAppModel().observe { [weak self](change) in + switch change { + case .change(let propertys): + guard let wallet = propertys.first(where: { $0.name == "currentWallet" })?.newValue as? WalletModel else { return } + guard wallet.address != self?.currentWallet?.address else { return } + self?.refresh() + default: + break + } + } + NotificationCenter.default.addObserver(self, selector: #selector(refreshPrice), name: .changeLocalCurrency, object: nil) + } + + func refresh() { + guard let appModel = WalletRealmTool.realm.objects(AppModel.self).first(where: { _ in true }) else { return } + guard let wallet = appModel.currentWallet else { return } + currentWallet = wallet + self.tokens = appModel.nativeTokenList.map({ Token($0) }) + self.tokens += wallet.selectTokenList.map({ Token($0) }) + self.tokens.forEach { (token) in + token.walletAddress = wallet.address + } + delegate?.walletPresenter(presenter: self, didRefreshToken: self.tokens) + refreshBalance() +// refreshPrice() + } + + func refreshBalance() { + refreshing = true + delegate?.walletPresenterBeganRefresh(presenter: self) + currency = LocalCurrencyService.shared.getLocalCurrencySelect() + delegate?.walletPresenter(presenter: self, didRefreshCurrency: currency) + let tokens = self.tokens + DispatchQueue.global().async { + var amount = 0.0 + tokens.forEach { (token) in + do { + let balance = try self.getBalance(token: token) + let balanceText = Web3Utils.formatToEthereumUnits(balance, toUnits: .eth, decimals: 8) ?? "0" + token.balance = Double(balanceText) + + token.price = self.getPrice(token: token) + + if let price = token.price, let balance = token.balance { + amount += price * balance + } + DispatchQueue.main.async { + self.delegate?.walletPresenter(presenter: self, didRefreshTokenAmount: token) + } + } catch { + } + } + DispatchQueue.main.async { + self.refreshing = false + self.delegate?.walletPresenterEndedRefresh(presenter: self) + self.delegate?.walletPresenter(presenter: self, didRefreshTotalAmount: amount) + } + } + } + + @objc func refreshPrice() { + refreshing = true + currency = LocalCurrencyService.shared.getLocalCurrencySelect() + delegate?.walletPresenter(presenter: self, didRefreshCurrency: currency) + delegate?.walletPresenterBeganRefresh(presenter: self) + let tokens = self.tokens + var amount = 0.0 + DispatchQueue.global().async { + tokens.forEach { (token) in + token.price = self.getPrice(token: token) + if let price = token.price, let balance = token.balance { + amount += price * balance + } + DispatchQueue.main.async { + self.delegate?.walletPresenter(presenter: self, didRefreshTokenAmount: token) + } + } + DispatchQueue.main.async { + self.refreshing = false + self.delegate?.walletPresenterEndedRefresh(presenter: self) + self.delegate?.walletPresenter(presenter: self, didRefreshTotalAmount: amount) + } + } + } + + // MARK: - Utils + func getBalance(token: Token) throws -> BigUInt { + switch token.type { + case .ether: + return try EthereumNetwork().getWeb3().eth.getBalance(address: EthereumAddress(token.walletAddress)!) + case .appChain: + return try AppChainNetwork.appChain().rpc.getBalance(address: token.walletAddress) + default: + fatalError() + } + } + + func getPrice(token: Token) -> Double? { + let currencyToken = CurrencyService().searchCurrencyId(for: token.symbol) + guard let tokenId = currencyToken?.id else { + return nil + } + return try? Promise.init { (resolver) in + CurrencyService().getCurrencyPrice(tokenid: tokenId, currencyType: currency.short, completion: { (result) in + switch result { + case .success(let price): + resolver.fulfill(price) + case .error(let error): + resolver.reject(error) + } + }) + }.wait() + } +} diff --git a/Neuron/Sections/Wallet/Home/WalletViewController.swift b/Neuron/Sections/Wallet/Home/WalletViewController.swift index 0e10ae9c..16f6e225 100644 --- a/Neuron/Sections/Wallet/Home/WalletViewController.swift +++ b/Neuron/Sections/Wallet/Home/WalletViewController.swift @@ -8,235 +8,135 @@ import UIKit import RealmSwift -import Web3swift -import BigInt -import PullToRefresh - -class WalletViewController: UITableViewController, SelectWalletControllerDelegate { - @IBOutlet var titleView: UIView! - @IBOutlet var tabHeader: UIView! - @IBOutlet weak var tabbedButtonView: TabbedButtonsView! - @IBOutlet weak var totleCurrencyLabel: UILabel! - @IBOutlet weak var currencyBalanceLabel: UILabel! - @IBOutlet weak var switchWalletButtonItem: UIBarButtonItem! - @IBOutlet var scanBarButtonItem: UIBarButtonItem! - @IBOutlet weak var requestPaymentButtonItem: UIBarButtonItem! - @IBOutlet weak var icon: UIImageView! - @IBOutlet weak var name: UILabel! - @IBOutlet weak var address: UILabel! - @IBOutlet weak var switchWalletButton: UIButton! - private var tokensViewController: TokensViewController! - private var nftViewController: UIViewController! - private var assetPageViewController: UIPageViewController! - private var isHeaderViewHidden = false { - didSet { - updateNavigationBar() - } - } - let refresher = PullToRefresh() - - override var preferredStatusBarStyle: UIStatusBarStyle { - return isHeaderViewHidden ? .default : .lightContent - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - didGetDataForCurrentWallet() - updateNavigationBar() - } +class WalletViewController: UIViewController { + @IBOutlet weak var tableView: UITableView! + @IBOutlet var tableHeadView: UIView! + @IBOutlet weak var refreshButton: UIButton! + @IBOutlet var addWalletBarButton: UIBarButtonItem! + @IBOutlet var switchWalletBarButton: UIBarButtonItem! + @IBOutlet weak var currencyLabel: UILabel! + @IBOutlet weak var totalAmountLabel: UILabel! + + private var presenter = WalletPresenter() + private var walletCountObserve: NotificationToken? +// private var observers = [NSKeyValueObservation]() override func viewDidLoad() { super.viewDidLoad() - automaticallyAdjustsScrollViewInsets = true - addNotify() - tokensViewController = storyboard!.instantiateViewController(withIdentifier: "tokensViewController") as? TokensViewController - tokensViewController.delegate = self - nftViewController = storyboard!.instantiateViewController(withIdentifier: "nftViewController") - assetPageViewController.setViewControllers([tokensViewController], direction: .forward, animated: false) - assetPageViewController.dataSource = self - assetPageViewController.delegate = self - tableView.addPullToRefresh(refresher) { - self.loadData() + // Do any additional setup after loading the view. + let refresh = UIRefreshControl() + refresh.addTarget(self, action: #selector(WalletViewController.refresh), for: .valueChanged) + tableView.refreshControl = refresh + presenter.delegate = self + presenter.refresh() + + // observe wallet count + walletCountObserve = WalletRealmTool.realm.objects(WalletModel.self).observe { [weak self](_) in + if WalletRealmTool.realm.objects(WalletModel.self).count > 1 { + self?.navigationItem.rightBarButtonItem = self?.switchWalletBarButton + } else { + self?.navigationItem.rightBarButtonItem = self?.addWalletBarButton + } } - tabbedButtonView.buttonTitles = ["代币", "藏品"] - tabbedButtonView.delegate = self - } - @objc private func endRefresh() { - tableView.endRefreshing(at: .top) - } - - private func loadData() { - NotificationCenter.default.post(name: .beginRefresh, object: nil) +// observers.append(presenter.observe(\.currency, options: [.initial]) { (_, _) in +// }) } + // MARK: - Navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.identifier == "embedAssetPages" { - assetPageViewController = segue.destination as? UIPageViewController - } - if segue.identifier == "requestPayment" { - let requestPaymentViewController = segue.destination as! RequestPaymentViewController - let appModel = WalletRealmTool.getCurrentAppModel() - requestPaymentViewController.appModel = appModel - } - if segue.identifier == "switchWallet" { - let selectWalletController = segue.destination as! SelectWalletController - selectWalletController.delegate = self - } } - @IBAction func copyWalletAddress(_ sender: UITapGestureRecognizer) { - copyAddress() + // MARK: - Actions + @IBAction func refresh() { + guard !presenter.refreshing else { return } + presenter.refresh() } - - @IBAction func copyWalletAddressWithButton(_ sender: UIButton) { - copyAddress() + @IBAction func walletQRCode(_ sender: Any) { } - - @IBAction func scanQRCode(_ sender: Any) { - let qrCodeViewController = QRCodeViewController() - qrCodeViewController.delegate = self - navigationController?.pushViewController(qrCodeViewController, animated: true) + @IBAction func transaction(_ sender: Any) { } +} - @IBAction func unwind(seque: UIStoryboardSegue) { } - - override func scrollViewDidScroll(_ scrollView: UIScrollView) { - var offset = scrollView.contentOffset.y - if #available(iOS 11.0, *) { - offset += scrollView.adjustedContentInset.top - } else { - offset += scrollView.contentInset.top - } +extension WalletViewController: WalletPresenterDelegate { + func walletPresenter(presenter: WalletPresenter, didRefreshTokenAmount token: Token) { + guard let index = presenter.tokens.lastIndex(where: { $0 == token }) else { return } + tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none) + } - isHeaderViewHidden = offset >= tableView.tableHeaderView!.bounds.height - [tokensViewController, nftViewController].forEach { listViewController in - (listViewController as? UITableViewController)?.tableView.isScrollEnabled = isHeaderViewHidden - } + func walletPresenter(presenter: WalletPresenter, didRefreshToken tokens: [Token]) { + tableView.reloadData() } - private func copyAddress() { - let appModel = WalletRealmTool.getCurrentAppModel() - UIPasteboard.general.string = appModel.currentWallet?.address - Toast.showToast(text: "地址已经复制到粘贴板") + func walletPresenter(presenter: WalletPresenter, didRefreshCurrency currency: LocalCurrency) { + currencyLabel.text = "总资产(\(currency.name))" } - private func updateNavigationBar() { - if isHeaderViewHidden { - navigationItem.rightBarButtonItems = [switchWalletButtonItem] - navigationItem.title = WalletRealmTool.getCurrentAppModel().currentWallet?.name - navigationItem.titleView = nil - } else { - navigationItem.rightBarButtonItems = [requestPaymentButtonItem] - navigationItem.titleView = titleView - } - setNeedsStatusBarAppearanceUpdate() - if WalletRealmTool.getCurrentAppModel().wallets.count == 1 { - switchWalletButton.setTitle("添加钱包", for: .normal) - switchWalletButton.setImage(UIImage(named: "add_wallet_icon")!, for: .normal) + func walletPresenter(presenter: WalletPresenter, didRefreshTotalAmount amount: Double) { + if amount == 0.0 { + totalAmountLabel.text = "暂无资产" } else { - switchWalletButton.setTitle("切换钱包", for: .normal) - switchWalletButton.setImage(UIImage(named: "switch_wallet_icon")!, for: .normal) + totalAmountLabel.text = "≈\(presenter.currency.symbol)" + String(format: "%.8lf", amount) } } - func addNotify() { - NotificationCenter.default.addObserver(self, selector: #selector(changeWallet(notification:)), name: .createWalletSuccess, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(endRefresh), name: .endRefresh, object: nil) - } - - func didGetDataForCurrentWallet() { - if WalletRealmTool.hasWallet() { - let walletModel = WalletRealmTool.getCurrentAppModel().currentWallet! - refreshUI(walletModel: walletModel) - loadData() + func walletPresenterBeganRefresh(presenter: WalletPresenter) { + if !tableView.refreshControl!.isRefreshing { + UIView.beginAnimations("refresh", context: nil) + UIView.setAnimationDuration(0.4) + UIView.setAnimationRepeatCount(Float(Int.max)) + UIView.setAnimationCurve(.linear) + refreshButton.transform = refreshButton.transform.rotated(by: CGFloat(Double.pi)) + UIView.commitAnimations() } } - //switch wallet delegate - func selectWalletController(_ controller: SelectWalletController, didSelectWallet model: WalletModel) { - didGetDataForCurrentWallet() - NotificationCenter.default.post(name: .switchWallet, object: nil) - } - - @objc private func changeWallet(notification: Notification) { - let address = notification.userInfo!["address"] as! String - let walletModel = WalletRealmTool.getCreatWallet(walletAddress: address) - refreshUI(walletModel: walletModel) - } - - func refreshUI(walletModel: WalletModel) { - name.text = walletModel.name - address.text = walletModel.address - icon.image = UIImage(data: walletModel.iconData) - } - - // MAKR: - UITableView Delegate - override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - if #available(iOS 11.0, *) { - return tableView.frame.height - tabHeader.frame.height - tableView.adjustedContentInset.top - tableView.adjustedContentInset.bottom + func walletPresenterEndedRefresh(presenter: WalletPresenter) { + if tableView.refreshControl!.isRefreshing { + tableView.refreshControl?.endRefreshing() } else { - return tableView.frame.height - tabHeader.frame.height - tableView.contentInset.top - tableView.contentInset.bottom + refreshButton.layer.removeAllAnimations() } } +} - override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - return tabHeader - } - - override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return tabHeader.frame.height - } - - deinit { - tableView.removePullToRefresh(at: .top) +extension WalletViewController: UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return presenter.tokens.count } -} -extension WalletViewController: QRCodeViewControllerDelegate { - func didBackQRCodeMessage(codeResult: String) { - guard let token = WalletRealmTool.getCurrentAppModel().nativeTokenList.first(where: { $0.symbol == "ETH" }) else { - return + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let token = presenter.tokens[indexPath.row] + let cell: TokenTableViewCell + if token.balance == nil { + cell = tableView.dequeueReusableCell(withIdentifier: "TokenTableViewCell_Loading") as! TokenTableViewCell + } else if token.price == nil { + cell = tableView.dequeueReusableCell(withIdentifier: "TokenTableViewCell_NoPrice") as! TokenTableViewCell + } else { + cell = tableView.dequeueReusableCell(withIdentifier: "TokenTableViewCell") as! TokenTableViewCell } - let controller: SendTransactionViewController = UIStoryboard(name: .sendTransaction).instantiateViewController() - controller.token = token - controller.recipientAddress = codeResult // TODO: At least do address validation here? - navigationController?.pushViewController(controller, animated: true) + cell.token = presenter.tokens[indexPath.row] + return cell } -} -extension WalletViewController: TabbedButtonsViewDelegate, UIPageViewControllerDataSource, UIPageViewControllerDelegate, TokensViewControllerDelegate { - func getCurrentCurrencyModel(currencyModel: LocalCurrency, totleCurrency: Double) { - totleCurrencyLabel.text = "总资产(\(currencyModel.name))" - currencyBalanceLabel.text = String(format: "%.2f", totleCurrency) + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + return tableHeadView } - func tabbedButtonsView(_ view: TabbedButtonsView, didSelectButtonAt index: Int) { - let viewControllerToShow = index == 0 ? tokensViewController : nftViewController - assetPageViewController.setViewControllers([viewControllerToShow!], direction: .forward, animated: false) + func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { + return false } - func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { - if viewController == nftViewController { - return tokensViewController - } - return nil + func scrollViewDidScroll(_ scrollView: UIScrollView) { + refreshButton.alpha = 1 - scrollView.contentOffset.y / -60 } - func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { - if viewController == tokensViewController { - return nftViewController - } - return nil + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + print(#function) + self.refreshButton.alpha = 1.0 + self.tableView.refreshControl?.endRefreshing() } +} - func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { - if completed { - if previousViewControllers.first == tokensViewController { - tabbedButtonView.selectedIndex = 1 - } else { - tabbedButtonView.selectedIndex = 0 - } - } - } +extension WalletViewController { } diff --git a/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift b/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift index 87d7d2db..ead19901 100644 --- a/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift +++ b/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift @@ -7,11 +7,40 @@ // import UIKit +import Web3swift class TokenTableViewCell: UITableViewCell { - @IBOutlet var tokenImage: UIImageView! - @IBOutlet var token: UILabel! - @IBOutlet var balance: UILabel! - @IBOutlet var currency: UILabel! - @IBOutlet var network: UILabel! + @IBOutlet weak var iconView: UIImageView! + @IBOutlet weak var symbolLabel: UILabel! + @IBOutlet weak var balanceLabel: UILabel! + @IBOutlet weak var amountLabel: UILabel! + @IBOutlet weak var symbolWidthConstraint: NSLayoutConstraint! + + var token: Token! { + didSet { + iconView.sd_setImage(with: URL(string: token.iconUrl ?? ""), placeholderImage: UIImage(named: "eth_logo")) + symbolLabel.text = token.symbol + symbolWidthConstraint.constant = symbolLabel.textRect(forBounds: CGRect(x: 0, y: 0, width: 150, height: 20), limitedToNumberOfLines: 1).size.width + if let balance = token.balance { + balanceLabel.text = String(balance) + if let price = token.price { + let amount = price * balance + let currency = LocalCurrencyService.shared.getLocalCurrencySelect() + amountLabel.text = "≈\(currency.symbol)" + String(format: "%.8lf", amount) + } + } + } + } + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + override func setSelected(_ selected: Bool, animated: Bool) { + super.setSelected(selected, animated: animated) + + // Configure the view for the selected state + } + } diff --git a/Neuron/Services/Common/Token.swift b/Neuron/Services/Common/Token.swift new file mode 100644 index 00000000..f9498927 --- /dev/null +++ b/Neuron/Services/Common/Token.swift @@ -0,0 +1,72 @@ +// +// Token.swift +// Neuron +// +// Created by 晨风 on 2018/11/20. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import Web3swift +import PromiseKit +import BigInt +import EthereumAddress + +class Token { +// var tokenBalance = "" +// var currencyAmount = "" + var name = "" + var iconUrl: String? = "" + var address = "" +// var decimals = 18 + var symbol = "" + var chainName: String? = "" + var chainId = "" + var chainHosts = "" + var isNativeToken = false + var walletAddress = "" + + var balance: Double? + var price: Double? + + init(_ token: TokenModel) { + name = token.name + iconUrl = token.iconUrl + address = token.address + symbol = token.symbol + chainId = token.chainId + chainName = token.chainName + chainHosts = token.chainHosts + isNativeToken = token.isNativeToken + } +} + +extension Token { + enum `Type`: String { + case ether + case erc20 + case appChain + case appChainErc20 + } + var type: Type { + if isNativeToken { + if chainId == NativeChainId.ethMainnetChainId { + return .ether + } else { + if address != "" { + return .appChainErc20 + } else { + return .appChain + } + } + } else { + return .erc20 + } + } +} + +extension Token: Equatable { + static func == (lhs: Token, rhs: Token) -> Bool { + return lhs.address == rhs.address && lhs.walletAddress == rhs.walletAddress && rhs.type == lhs.type + } +} From 5d3e394e09d9b7a2fa11b9263edb6da82faea137 Mon Sep 17 00:00:00 2001 From: cezres Date: Tue, 20 Nov 2018 18:07:38 +0800 Subject: [PATCH 119/315] Modify dapp device motion handler --- .../Dapp/Native/DAppDeviceMotionMessageHandler.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Neuron/Sections/Dapp/Native/DAppDeviceMotionMessageHandler.swift b/Neuron/Sections/Dapp/Native/DAppDeviceMotionMessageHandler.swift index 2f661c6e..8d7e45ee 100644 --- a/Neuron/Sections/Dapp/Native/DAppDeviceMotionMessageHandler.swift +++ b/Neuron/Sections/Dapp/Native/DAppDeviceMotionMessageHandler.swift @@ -70,9 +70,9 @@ class DAppDeviceMotionMessageHandler: DAppNativeMessageHandler { func motionDidUpdate(motion: CMDeviceMotion) { let result: [String: Any] = [ - "alpha": motion.attitude.roll, - "beta": motion.attitude.pitch, - "gamma": motion.attitude.yaw + "alpha": motion.attitude.roll / Double.pi * 180, // -180 ~ 180 + "gamma": -motion.attitude.pitch / Double.pi * 180, // -90 ~ 90 + "beta": motion.attitude.yaw / Double.pi * 180 // -180 ~ 180 ] callback(funcName: "onDeviceMotionChange", result: ["res": result]) } From 62d5b203ef78aa1c6c853a339a097d7e68c3d3fa Mon Sep 17 00:00:00 2001 From: LuFP Date: Tue, 20 Nov 2018 18:57:32 +0800 Subject: [PATCH 120/315] Add back button and forwar button --- .../Dapp/WebView/BrowserViewController.swift | 55 ++++++++++----- .../Dapp/WebView/DAppBrowser.storyboard | 68 +++++++------------ 2 files changed, 62 insertions(+), 61 deletions(-) diff --git a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift index aa8c903c..a483614a 100644 --- a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift @@ -10,10 +10,19 @@ import UIKit import WebKit class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipeBackable { - @IBOutlet weak var closeButton: UIButton! + @IBOutlet weak var directionView: UIView! + @IBOutlet weak var backButton: UIButton! + @IBOutlet weak var forwardButton: UIButton! var requestUrlStr = "" var mainUrl: URL? var webViewProgressObservation: NSKeyValueObservation! + var viewFrameCGRect: CGRect { + if #available(iOS 11.0, *) { + return self.view.safeAreaLayoutGuide.layoutFrame + } else { + return self.view.frame + } + } lazy var webView: WKWebView = { let webView = WKWebView( frame: .zero, @@ -47,7 +56,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe }() override func viewDidLayoutSubviews() { - self.webView.frame = self.view.bounds + self.webView.frame = viewFrameCGRect } override func viewWillAppear(_ animated: Bool) { @@ -60,6 +69,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe super.viewDidLoad() view.addSubview(webView) view.addSubview(progressView) + view.bringSubviewToFront(directionView) requestUrlStr = requestUrlStr.trimmingCharacters(in: .whitespaces) mainUrl = URL(string: getRequestStr(requestStr: requestUrlStr)) if let url = mainUrl { @@ -101,15 +111,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe } } - @IBAction func didClickBackButton(_ sender: UIButton) { - if webView.canGoBack { - webView.goBack() - } else { - navigationController?.popViewController(animated: true) - } - } - - @IBAction func dudCkucjCloseButton(_ sender: UIButton) { + @IBAction func didClickCloseButton(_ sender: UIBarButtonItem) { navigationController?.popViewController(animated: true) } @@ -126,6 +128,31 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe webView.evaluateJavaScript(script, completionHandler: nil) } + func adjustmentDirectionButton() { + if webView.canGoBack { + if directionView.isHidden == true { + directionView.isHidden = false + webView.frame = CGRect(x: viewFrameCGRect.origin.x, y: viewFrameCGRect.origin.y, width: viewFrameCGRect.size.width, height: viewFrameCGRect.size.height - 50) + } + backButton.isSelected = true + } else { + backButton.isSelected = false + } + if webView.canGoForward { + forwardButton.isSelected = true + } else { + forwardButton.isSelected = false + } + } + + @IBAction func backButtonAction(_ sender: UIButton) { + webView.goBack() + } + + @IBAction func forwardButtonAction(_ sender: UIButton) { + webView.goForward() + } + deinit { webViewProgressObservation.invalidate() } @@ -223,11 +250,7 @@ extension BrowserViewController { extension BrowserViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { title = webView.title - if webView.canGoBack { - closeButton.isHidden = false - } else { - closeButton.isHidden = true - } + adjustmentDirectionButton() let relJs = "document.querySelector('head').querySelector('link[rel=manifest]').href;" let refJs = "document.querySelector('head').querySelector('link[ref=manifest]').href;" webView.evaluateJavaScript(relJs) { (manifest, _) in diff --git a/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard b/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard index e4fdacc9..9300fa73 100644 --- a/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard +++ b/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard @@ -84,7 +84,7 @@ - + @@ -125,52 +139,17 @@ - - - - - - - - - - - + + + + + - + + + @@ -752,7 +731,6 @@ - From b79bd4e9df41fc7bab11b71601f6e346430aa4df Mon Sep 17 00:00:00 2001 From: LuFP Date: Tue, 20 Nov 2018 19:17:53 +0800 Subject: [PATCH 121/315] Add UIViewController+Extension --- Neuron.xcodeproj/project.pbxproj | 4 ++++ .../UIKit/UIViewController+Extension.swift | 19 +++++++++++++++++++ .../Dapp/WebView/BrowserViewController.swift | 11 ++--------- .../Dapp/WebView/DappViewController.swift | 2 +- 4 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 Neuron/Extensions/UIKit/UIViewController+Extension.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index b41ae347..30ef2ba3 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -185,6 +185,7 @@ 89D84CEB2148C03B006B0287 /* NFTHeaderReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D84CEA2148C03B006B0287 /* NFTHeaderReusableView.swift */; }; 89D84CED2148C056006B0287 /* NFTFooterReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D84CEC2148C056006B0287 /* NFTFooterReusableView.swift */; }; 89D935252139363A002FAEEC /* TokenTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D935242139363A002FAEEC /* TokenTableViewCell.swift */; }; + 89ECBE2421A423B8005D3775 /* UIViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89ECBE2321A423B8005D3775 /* UIViewController+Extension.swift */; }; 89F7966D213A6B680064808A /* ERC721TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F7966C213A6B680064808A /* ERC721TableViewCell.swift */; }; 89F7966F213BD5680064808A /* Assets.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 89F7966E213BD5680064808A /* Assets.storyboard */; }; 89F79671213BD8C40064808A /* TransactionHistory.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 89F79670213BD8C40064808A /* TransactionHistory.storyboard */; }; @@ -406,6 +407,7 @@ 89D84CEA2148C03B006B0287 /* NFTHeaderReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFTHeaderReusableView.swift; sourceTree = ""; }; 89D84CEC2148C056006B0287 /* NFTFooterReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFTFooterReusableView.swift; sourceTree = ""; }; 89D935242139363A002FAEEC /* TokenTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenTableViewCell.swift; sourceTree = ""; }; + 89ECBE2321A423B8005D3775 /* UIViewController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extension.swift"; sourceTree = ""; }; 89F7966C213A6B680064808A /* ERC721TableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ERC721TableViewCell.swift; sourceTree = ""; }; 89F7966E213BD5680064808A /* Assets.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Assets.storyboard; sourceTree = ""; }; 89F79670213BD8C40064808A /* TransactionHistory.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TransactionHistory.storyboard; sourceTree = ""; }; @@ -548,6 +550,7 @@ 6EF8F40B2175CAD9004B7587 /* UIControl+Extension.swift */, 6E9F9D55219AA71F009ED8B2 /* UINavigationBar+FixSpace.swift */, 6E9F9D59219AAF99009ED8B2 /* UINavigationItem+FixSpace.swift */, + 89ECBE2321A423B8005D3775 /* UIViewController+Extension.swift */, ); path = UIKit; sourceTree = ""; @@ -1390,6 +1393,7 @@ 89FE21D820EC7E1800A09302 /* ERC20TokenService.swift in Sources */, 6E3B2B6B219D938F0095257D /* SentTransaction.swift in Sources */, 1A5515E521898A1000D34791 /* WalletKeystoreManager.swift in Sources */, + 89ECBE2421A423B8005D3775 /* UIViewController+Extension.swift in Sources */, 1A09224321357F3D00CAED5D /* TokensViewController.swift in Sources */, 6EABFB10219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift in Sources */, 6EF8F40C2175CAD9004B7587 /* UIControl+Extension.swift in Sources */, diff --git a/Neuron/Extensions/UIKit/UIViewController+Extension.swift b/Neuron/Extensions/UIKit/UIViewController+Extension.swift new file mode 100644 index 00000000..4c694108 --- /dev/null +++ b/Neuron/Extensions/UIKit/UIViewController+Extension.swift @@ -0,0 +1,19 @@ +// +// UIViewController+Extension.swift +// Neuron +// +// Created by XiaoLu on 2018/11/20. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit + +extension UIViewController { + var safeAreaFrame: CGRect { + if #available(iOS 11.0, *) { + return view.safeAreaLayoutGuide.layoutFrame + } else { + return view.frame + } + } +} diff --git a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift index a483614a..c95f415d 100644 --- a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift @@ -16,13 +16,6 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe var requestUrlStr = "" var mainUrl: URL? var webViewProgressObservation: NSKeyValueObservation! - var viewFrameCGRect: CGRect { - if #available(iOS 11.0, *) { - return self.view.safeAreaLayoutGuide.layoutFrame - } else { - return self.view.frame - } - } lazy var webView: WKWebView = { let webView = WKWebView( frame: .zero, @@ -56,7 +49,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe }() override func viewDidLayoutSubviews() { - self.webView.frame = viewFrameCGRect + self.webView.frame = safeAreaFrame } override func viewWillAppear(_ animated: Bool) { @@ -132,7 +125,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe if webView.canGoBack { if directionView.isHidden == true { directionView.isHidden = false - webView.frame = CGRect(x: viewFrameCGRect.origin.x, y: viewFrameCGRect.origin.y, width: viewFrameCGRect.size.width, height: viewFrameCGRect.size.height - 50) + webView.frame = CGRect(x: safeAreaFrame.origin.x, y: safeAreaFrame.origin.y, width: safeAreaFrame.size.width, height: safeAreaFrame.size.height - 50) } backButton.isSelected = true } else { diff --git a/Neuron/Sections/Dapp/WebView/DappViewController.swift b/Neuron/Sections/Dapp/WebView/DappViewController.swift index 7981e6cf..dde0ee8b 100644 --- a/Neuron/Sections/Dapp/WebView/DappViewController.swift +++ b/Neuron/Sections/Dapp/WebView/DappViewController.swift @@ -70,7 +70,7 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, } override func viewDidLayoutSubviews() { - self.webView.frame = self.view.bounds + self.webView.frame = safeAreaFrame } //scrollView代理 From a20be44dc6b892dd4f5011a8b55603038dcc9c6b Mon Sep 17 00:00:00 2001 From: LuFP Date: Wed, 21 Nov 2018 10:41:41 +0800 Subject: [PATCH 122/315] Optimizate webView forwardButton.isSelected --- Neuron/Sections/Dapp/WebView/BrowserViewController.swift | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift index c95f415d..9d61620c 100644 --- a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift @@ -123,7 +123,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe func adjustmentDirectionButton() { if webView.canGoBack { - if directionView.isHidden == true { + if directionView.isHidden { directionView.isHidden = false webView.frame = CGRect(x: safeAreaFrame.origin.x, y: safeAreaFrame.origin.y, width: safeAreaFrame.size.width, height: safeAreaFrame.size.height - 50) } @@ -131,11 +131,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe } else { backButton.isSelected = false } - if webView.canGoForward { - forwardButton.isSelected = true - } else { - forwardButton.isSelected = false - } + forwardButton.isSelected = webView.canGoForward } @IBAction func backButtonAction(_ sender: UIButton) { From 9729874b77827e5d7104f18ad597a6218af99f9c Mon Sep 17 00:00:00 2001 From: LuFP Date: Wed, 21 Nov 2018 10:43:53 +0800 Subject: [PATCH 123/315] Fix when have no BiometryType to hide tableviewCell --- .../Sections/Settings/SettingsViewController.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Neuron/Sections/Settings/SettingsViewController.swift b/Neuron/Sections/Settings/SettingsViewController.swift index 5a948cb2..702ad8a2 100644 --- a/Neuron/Sections/Settings/SettingsViewController.swift +++ b/Neuron/Sections/Settings/SettingsViewController.swift @@ -42,6 +42,20 @@ class SettingsViewController: UITableViewController { } } + override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + if indexPath.section == 1 && indexPath.row == 1 && !AuthenticationService.shared.isValid { + cell.isHidden = true + } + } + + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + if indexPath.section == 1 && indexPath.row == 1 && !AuthenticationService.shared.isValid { + return 0 + } else { + return super.tableView(tableView, heightForRowAt: indexPath) + } + } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) if indexPath.section == 2 { From 1279b66e607854a14f7998a6ab40a92b3497efbb Mon Sep 17 00:00:00 2001 From: cezres Date: Wed, 21 Nov 2018 11:23:58 +0800 Subject: [PATCH 124/315] Update project.pbxproj --- Neuron.xcodeproj/project.pbxproj | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index b41ae347..e15f9590 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -72,6 +72,10 @@ 6E876845218031670032EBCE /* UInt+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E876844218031670032EBCE /* UInt+Extension.swift */; }; 6E8962032194575B00D2C0AD /* DoubleExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8962022194575B00D2C0AD /* DoubleExtensionTests.swift */; }; 6E90D06921704C1D00B98363 /* SensorsAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E90D06821704C1D00B98363 /* SensorsAnalytics.swift */; }; + 6E9B947B21A4080300D67357 /* DesignableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B947A21A4080300D67357 /* DesignableButton.swift */; }; + 6E9B947D21A4083100D67357 /* DesignableGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B947C21A4083100D67357 /* DesignableGradientView.swift */; }; + 6E9B947F21A4086400D67357 /* WalletPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B947E21A4086400D67357 /* WalletPresenter.swift */; }; + 6E9B948121A408B400D67357 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B948021A408B400D67357 /* Token.swift */; }; 6E9D7CBA216B04EF0044176D /* AuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9D7CB9216B04EF0044176D /* AuthenticationService.swift */; }; 6E9D7CBE216B386F0044176D /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9D7CBC216B386F0044176D /* AuthenticationViewController.swift */; }; 6E9D7CC1216B3AE40044176D /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E9D7CC0216B3AE40044176D /* Authentication.storyboard */; }; @@ -284,6 +288,10 @@ 6E876844218031670032EBCE /* UInt+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt+Extension.swift"; sourceTree = ""; }; 6E8962022194575B00D2C0AD /* DoubleExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoubleExtensionTests.swift; sourceTree = ""; }; 6E90D06821704C1D00B98363 /* SensorsAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorsAnalytics.swift; sourceTree = ""; }; + 6E9B947A21A4080300D67357 /* DesignableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignableButton.swift; sourceTree = ""; }; + 6E9B947C21A4083100D67357 /* DesignableGradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignableGradientView.swift; sourceTree = ""; }; + 6E9B947E21A4086400D67357 /* WalletPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletPresenter.swift; sourceTree = ""; }; + 6E9B948021A408B400D67357 /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = ""; }; 6E9D7CB9216B04EF0044176D /* AuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationService.swift; sourceTree = ""; }; 6E9D7CBC216B386F0044176D /* AuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = ""; }; 6E9D7CC0216B3AE40044176D /* Authentication.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Authentication.storyboard; sourceTree = ""; }; @@ -780,6 +788,8 @@ 8964A17F20BE94F70086848F /* NEPickerView.swift */, 1A207DD42137D8CF008DC306 /* TabbedButtonsView.swift */, 1A207DD62137D8F5008DC306 /* TabbedButtonsView.xib */, + 6E9B947A21A4080300D67357 /* DesignableButton.swift */, + 6E9B947C21A4083100D67357 /* DesignableGradientView.swift */, ); path = Common; sourceTree = ""; @@ -921,6 +931,7 @@ children = ( 1A2BFA19213513D60065496B /* Wallet.storyboard */, 8928C27820B2A61900C3103E /* WalletViewController.swift */, + 6E9B947E21A4086400D67357 /* WalletPresenter.swift */, 1A09224221357F3D00CAED5D /* TokensViewController.swift */, 1A092244213580C900CAED5D /* NFTViewController.swift */, 89D84CEA2148C03B006B0287 /* NFTHeaderReusableView.swift */, @@ -1019,6 +1030,7 @@ 6E7D3837219C173D0031177B /* TransactionDetails.swift */, 6E3B2B6A219D938F0095257D /* SentTransaction.swift */, 6E3B2B74219EDE200095257D /* TransactionStatusManager.swift */, + 6E9B948021A408B400D67357 /* Token.swift */, ); path = Common; sourceTree = ""; @@ -1375,6 +1387,7 @@ 1AEBF3E4219BA1E700653FF5 /* BigUInt+Extension.swift in Sources */, 1A6007A221A2AA9A00C7B712 /* PageItemAppearance.swift in Sources */, 6E7D3842219C47940031177B /* TransactionHistoryPresenter.swift in Sources */, + 6E9B947D21A4083100D67357 /* DesignableGradientView.swift in Sources */, 89B895B120F8B0CD00B9468B /* BrowserViewController.swift in Sources */, 1A207DCF2137B3F7008DC306 /* RequestPaymentViewController.swift in Sources */, 89D84CE72147C671006B0287 /* TraitsCollectionViewCell.swift in Sources */, @@ -1385,6 +1398,7 @@ 6E9F9D56219AA71F009ED8B2 /* UINavigationBar+FixSpace.swift in Sources */, 6E851CD2217DBB3D00769E00 /* TransactionHistoryService.swift in Sources */, 898A1A0F20B6539800ECB465 /* AddAssetTableViewCell.swift in Sources */, + 6E9B947B21A4080300D67357 /* DesignableButton.swift in Sources */, 89CA1FAE213F832800669B2C /* CurrencyService.swift in Sources */, 89A87C892173097000588674 /* WalletNameValidator.swift in Sources */, 89FE21D820EC7E1800A09302 /* ERC20TokenService.swift in Sources */, @@ -1450,6 +1464,8 @@ 1A60B6FC2186DB1C00FEFB3A /* Web3Utils.swift in Sources */, 1A75EBFF21A194B2008D484B /* SendTransactionViewController.swift in Sources */, 6EF8F4102176029E004B7587 /* OpenAuthViewController.swift in Sources */, + 6E9B948121A408B400D67357 /* Token.swift in Sources */, + 6E9B947F21A4086400D67357 /* WalletPresenter.swift in Sources */, 89C3BF09213521DE00872BD0 /* UIView+Extension.swift in Sources */, 896D042D20AE69C1002CFF6A /* AppDelegate.swift in Sources */, 6E867E03216C3EC800BD6FE5 /* AuthSelectWalletViewController.swift in Sources */, From c65929aab2979d61eb1b740a5dbf9a061895bbce Mon Sep 17 00:00:00 2001 From: cezres Date: Wed, 21 Nov 2018 12:01:29 +0800 Subject: [PATCH 125/315] Change of wallet token list --- Neuron/Sections/Wallet/Home/Wallet.storyboard | 15 +++- .../Wallet/Home/WalletPresenter.swift | 71 ++++++++++++++----- .../Wallet/Home/WalletViewController.swift | 36 ++++++---- .../TableViewCell/TokenTableViewCell.swift | 14 +--- 4 files changed, 91 insertions(+), 45 deletions(-) diff --git a/Neuron/Sections/Wallet/Home/Wallet.storyboard b/Neuron/Sections/Wallet/Home/Wallet.storyboard index b4fec93a..7547b852 100644 --- a/Neuron/Sections/Wallet/Home/Wallet.storyboard +++ b/Neuron/Sections/Wallet/Home/Wallet.storyboard @@ -1112,6 +1112,9 @@ + + + @@ -1128,13 +1131,21 @@ + + + + + + + + - + @@ -1142,7 +1153,7 @@ - + diff --git a/Neuron/Sections/Wallet/Home/WalletPresenter.swift b/Neuron/Sections/Wallet/Home/WalletPresenter.swift index 1f5bbea7..f2fa78dc 100644 --- a/Neuron/Sections/Wallet/Home/WalletPresenter.swift +++ b/Neuron/Sections/Wallet/Home/WalletPresenter.swift @@ -16,6 +16,7 @@ import EthereumAddress protocol WalletPresenterDelegate: NSObjectProtocol { func walletPresenterBeganRefresh(presenter: WalletPresenter) func walletPresenterEndedRefresh(presenter: WalletPresenter) + func walletPresenter(presenter: WalletPresenter, didSwitchWallet wallet: WalletModel) func walletPresenter(presenter: WalletPresenter, didRefreshToken tokens: [Token]) func walletPresenter(presenter: WalletPresenter, didRefreshTotalAmount amount: Double) func walletPresenter(presenter: WalletPresenter, didRefreshCurrency currency: LocalCurrency) @@ -25,14 +26,22 @@ protocol WalletPresenterDelegate: NSObjectProtocol { class WalletPresenter { var currentWallet: WalletModel? var currency: LocalCurrency! - private (set) var tokens = [Token]() + private (set) var tokens = [Token]() { + didSet { + timestamp = Date().timeIntervalSince1970 + delegate?.walletPresenter(presenter: self, didRefreshToken: self.tokens) + refreshAmount() + } + } weak var delegate: WalletPresenterDelegate? - private var notificationToken: NotificationToken? + private var walletObserver: NotificationToken? + private var tokenObserver: NotificationToken? private(set) var refreshing = false + private var timestamp: TimeInterval = 0.0 init() { - notificationToken = WalletRealmTool.getCurrentAppModel().observe { [weak self](change) in + walletObserver = WalletRealmTool.getCurrentAppModel().observe { [weak self](change) in switch change { case .change(let propertys): guard let wallet = propertys.first(where: { $0.name == "currentWallet" })?.newValue as? WalletModel else { return } @@ -43,36 +52,61 @@ class WalletPresenter { } } NotificationCenter.default.addObserver(self, selector: #selector(refreshPrice), name: .changeLocalCurrency, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(refreshAmount), name: .switchEthNetwork, object: nil) } func refresh() { guard let appModel = WalletRealmTool.realm.objects(AppModel.self).first(where: { _ in true }) else { return } guard let wallet = appModel.currentWallet else { return } currentWallet = wallet - self.tokens = appModel.nativeTokenList.map({ Token($0) }) - self.tokens += wallet.selectTokenList.map({ Token($0) }) - self.tokens.forEach { (token) in - token.walletAddress = wallet.address - } - delegate?.walletPresenter(presenter: self, didRefreshToken: self.tokens) - refreshBalance() -// refreshPrice() + delegate?.walletPresenter(presenter: self, didSwitchWallet: currentWallet!) + + tokenObserver?.invalidate() + tokenObserver = wallet.selectTokenList.observe({ [weak self](change) in + guard let self = self else { return } + switch change { + case .initial(let selectTokenList): + var tokens = [Token]() + tokens += WalletRealmTool.getCurrentAppModel().nativeTokenList.map({ Token($0) }) + tokens += selectTokenList.map({ Token($0) }) + tokens.forEach { (token) in + token.walletAddress = self.currentWallet!.address + } + self.tokens = tokens + case .update(let selectTokenList, let deletions, let insertions, modifications: _): + var tokens = self.tokens + let selectTokenFirstIndex = tokens.count - selectTokenList.count - deletions.count + deletions.enumerated().forEach({ (offset, element) in + let index = selectTokenFirstIndex + element - offset + tokens.remove(at: index) + }) + insertions.forEach({ (idx) in + let token = Token(selectTokenList[idx]) + token.walletAddress = self.currentWallet!.address + tokens.append(token) + }) + self.tokens = tokens + default: + break + } + }) } - func refreshBalance() { + @objc func refreshAmount() { refreshing = true delegate?.walletPresenterBeganRefresh(presenter: self) currency = LocalCurrencyService.shared.getLocalCurrencySelect() delegate?.walletPresenter(presenter: self, didRefreshCurrency: currency) + let timestamp = self.timestamp let tokens = self.tokens DispatchQueue.global().async { var amount = 0.0 tokens.forEach { (token) in + guard timestamp == self.timestamp else { return } do { let balance = try self.getBalance(token: token) let balanceText = Web3Utils.formatToEthereumUnits(balance, toUnits: .eth, decimals: 8) ?? "0" token.balance = Double(balanceText) - token.price = self.getPrice(token: token) if let price = token.price, let balance = token.balance { @@ -84,6 +118,7 @@ class WalletPresenter { } catch { } } + guard timestamp == self.timestamp else { return } DispatchQueue.main.async { self.refreshing = false self.delegate?.walletPresenterEndedRefresh(presenter: self) @@ -122,10 +157,14 @@ class WalletPresenter { switch token.type { case .ether: return try EthereumNetwork().getWeb3().eth.getBalance(address: EthereumAddress(token.walletAddress)!) - case .appChain: + case .appChain, .appChainErc20: return try AppChainNetwork.appChain().rpc.getBalance(address: token.walletAddress) - default: - fatalError() + case .erc20: + let contractAddress = EthereumAddress(token.address)! + let walletAddress = EthereumAddress(token.walletAddress)! + let contract = EthereumNetwork().getWeb3().contract(Web3.Utils.erc20ABI, at: contractAddress, abiVersion: 2)! + let result = try contract.method("balanceOf", parameters: [walletAddress as AnyObject])?.call() + return result?["0"] as! BigUInt } } diff --git a/Neuron/Sections/Wallet/Home/WalletViewController.swift b/Neuron/Sections/Wallet/Home/WalletViewController.swift index 16f6e225..23b25310 100644 --- a/Neuron/Sections/Wallet/Home/WalletViewController.swift +++ b/Neuron/Sections/Wallet/Home/WalletViewController.swift @@ -50,7 +50,7 @@ class WalletViewController: UIViewController { // MARK: - Actions @IBAction func refresh() { guard !presenter.refreshing else { return } - presenter.refresh() + presenter.refreshAmount() } @IBAction func walletQRCode(_ sender: Any) { } @@ -61,7 +61,9 @@ class WalletViewController: UIViewController { extension WalletViewController: WalletPresenterDelegate { func walletPresenter(presenter: WalletPresenter, didRefreshTokenAmount token: Token) { guard let index = presenter.tokens.lastIndex(where: { $0 == token }) else { return } + tableView.beginUpdates() tableView.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none) + tableView.endUpdates() } func walletPresenter(presenter: WalletPresenter, didRefreshToken tokens: [Token]) { @@ -72,31 +74,33 @@ extension WalletViewController: WalletPresenterDelegate { currencyLabel.text = "总资产(\(currency.name))" } + func walletPresenter(presenter: WalletPresenter, didSwitchWallet wallet: WalletModel) { + totalAmountLabel.text = "- - -" + title = wallet.name + } + func walletPresenter(presenter: WalletPresenter, didRefreshTotalAmount amount: Double) { if amount == 0.0 { totalAmountLabel.text = "暂无资产" } else { - totalAmountLabel.text = "≈\(presenter.currency.symbol)" + String(format: "%.8lf", amount) + totalAmountLabel.text = "≈\(presenter.currency.symbol)" + String(format: "%.4lf", amount) } } func walletPresenterBeganRefresh(presenter: WalletPresenter) { - if !tableView.refreshControl!.isRefreshing { - UIView.beginAnimations("refresh", context: nil) - UIView.setAnimationDuration(0.4) - UIView.setAnimationRepeatCount(Float(Int.max)) - UIView.setAnimationCurve(.linear) - refreshButton.transform = refreshButton.transform.rotated(by: CGFloat(Double.pi)) - UIView.commitAnimations() - } + UIView.beginAnimations("refresh", context: nil) + UIView.setAnimationDuration(0.4) + UIView.setAnimationRepeatCount(Float(Int.max)) + UIView.setAnimationCurve(.linear) + refreshButton.transform = refreshButton.transform.rotated(by: CGFloat(Double.pi)) + UIView.commitAnimations() } func walletPresenterEndedRefresh(presenter: WalletPresenter) { if tableView.refreshControl!.isRefreshing { tableView.refreshControl?.endRefreshing() - } else { - refreshButton.layer.removeAllAnimations() } + refreshButton.layer.removeAllAnimations() } } @@ -127,12 +131,16 @@ extension WalletViewController: UITableViewDataSource, UITableViewDelegate { return false } + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + } + func scrollViewDidScroll(_ scrollView: UIScrollView) { - refreshButton.alpha = 1 - scrollView.contentOffset.y / -60 + refreshButton.alpha = 1 - scrollView.contentOffset.y / -125 } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - print(#function) self.refreshButton.alpha = 1.0 self.tableView.refreshControl?.endRefreshing() } diff --git a/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift b/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift index ead19901..5761509a 100644 --- a/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift +++ b/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift @@ -26,21 +26,9 @@ class TokenTableViewCell: UITableViewCell { if let price = token.price { let amount = price * balance let currency = LocalCurrencyService.shared.getLocalCurrencySelect() - amountLabel.text = "≈\(currency.symbol)" + String(format: "%.8lf", amount) + amountLabel.text = "≈\(currency.symbol)" + String(format: "%.4lf", amount) } } } } - - override func awakeFromNib() { - super.awakeFromNib() - // Initialization code - } - - override func setSelected(_ selected: Bool, animated: Bool) { - super.setSelected(selected, animated: animated) - - // Configure the view for the selected state - } - } From 67d081a0f057778e431c396e2c5538ab0b0fa9f6 Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 21 Nov 2018 13:06:42 +0900 Subject: [PATCH 126/315] Enable DApp home view's scroll bounces --- .../Sections/Dapp/WebView/DAppBrowser.storyboard | 12 ++++++------ .../Sections/Dapp/WebView/DappViewController.swift | 14 ++++---------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard b/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard index 9300fa73..3ee25480 100644 --- a/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard +++ b/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard @@ -74,7 +74,7 @@ - + @@ -657,7 +657,7 @@ - + @@ -672,7 +672,7 @@ - + @@ -690,7 +690,7 @@ - + @@ -708,7 +708,7 @@ - + @@ -726,7 +726,7 @@ - + diff --git a/Neuron/Sections/Dapp/WebView/DappViewController.swift b/Neuron/Sections/Dapp/WebView/DappViewController.swift index dde0ee8b..4231344f 100644 --- a/Neuron/Sections/Dapp/WebView/DappViewController.swift +++ b/Neuron/Sections/Dapp/WebView/DappViewController.swift @@ -10,7 +10,8 @@ import UIKit import WebKit import JavaScriptCore import Toast_Swift -class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler, UIScrollViewDelegate, ErrorOverlayPresentable { + +class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, UIScrollViewDelegate, ErrorOverlayPresentable { private var webView = WKWebView() private var mainUrl: URL? @@ -18,11 +19,6 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, return .default } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - super.navigationController?.setNavigationBarHidden(false, animated: true) - } - override func viewDidLoad() { super.viewDidLoad() didAddSubLayout() @@ -62,7 +58,6 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, } else { // Fallback on earlier versions } - webView.scrollView.bounces = false webView.scrollView.delegate = self webView.navigationDelegate = self webView.uiDelegate = self @@ -73,12 +68,10 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, self.webView.frame = safeAreaFrame } - //scrollView代理 func viewForZooming(in scrollView: UIScrollView) -> UIView? { return nil } - //wkwebview func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if navigationAction.navigationType == .linkActivated { decisionHandler(.cancel) @@ -101,8 +94,9 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, } showOverlay() } +} - //WKScriptMessageHandler +extension DappViewController: WKScriptMessageHandler { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { switch message.name { case "pushSearchView": From 9d2387a8edf42a109aca4debbc660a69eb2e4296 Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 21 Nov 2018 13:15:43 +0900 Subject: [PATCH 127/315] Disallow zooming on DApp views --- Neuron/Base.lproj/Main.storyboard | 2 +- .../Dapp/WebView/BrowserViewController.swift | 11 +++++++++++ .../Dapp/WebView/DappViewController.swift | 16 +++++++++++----- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/Neuron/Base.lproj/Main.storyboard b/Neuron/Base.lproj/Main.storyboard index f54ea46c..52d58de2 100644 --- a/Neuron/Base.lproj/Main.storyboard +++ b/Neuron/Base.lproj/Main.storyboard @@ -106,7 +106,7 @@ - + diff --git a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift index 9d61620c..e5e4b6d9 100644 --- a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift @@ -31,6 +31,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe }) webView.navigationDelegate = self webView.uiDelegate = self + webView.scrollView.delegate = self webView.addAllNativeFunctionHandler() return webView }() @@ -217,6 +218,16 @@ extension BrowserViewController: WKUIDelegate { } } +extension BrowserViewController: UIScrollViewDelegate { + func viewForZooming(in scrollView: UIScrollView) -> UIView? { + return nil + } + + func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) { + scrollView.pinchGestureRecognizer?.isEnabled = false + } +} + extension BrowserViewController { private func pushTransaction(dappCommonModel: DAppCommonModel) { let contractController = storyboard!.instantiateViewController(withIdentifier: "contractController") as! ContractController diff --git a/Neuron/Sections/Dapp/WebView/DappViewController.swift b/Neuron/Sections/Dapp/WebView/DappViewController.swift index 4231344f..9b3bbbbb 100644 --- a/Neuron/Sections/Dapp/WebView/DappViewController.swift +++ b/Neuron/Sections/Dapp/WebView/DappViewController.swift @@ -11,7 +11,7 @@ import WebKit import JavaScriptCore import Toast_Swift -class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, UIScrollViewDelegate, ErrorOverlayPresentable { +class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, ErrorOverlayPresentable { private var webView = WKWebView() private var mainUrl: URL? @@ -68,10 +68,6 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, self.webView.frame = safeAreaFrame } - func viewForZooming(in scrollView: UIScrollView) -> UIView? { - return nil - } - func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if navigationAction.navigationType == .linkActivated { decisionHandler(.cancel) @@ -107,3 +103,13 @@ extension DappViewController: WKScriptMessageHandler { } } } + +extension DappViewController: UIScrollViewDelegate { + func viewForZooming(in scrollView: UIScrollView) -> UIView? { + return nil + } + + func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) { + scrollView.pinchGestureRecognizer?.isEnabled = false + } +} From b010539f702d302833bdb9772785ebd067f2f260 Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 21 Nov 2018 13:47:14 +0900 Subject: [PATCH 128/315] Position DApp web view properly with auto layout --- .../Dapp/WebView/DappViewController.swift | 53 +++++++++---------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/Neuron/Sections/Dapp/WebView/DappViewController.swift b/Neuron/Sections/Dapp/WebView/DappViewController.swift index 9b3bbbbb..e7ab8aa4 100644 --- a/Neuron/Sections/Dapp/WebView/DappViewController.swift +++ b/Neuron/Sections/Dapp/WebView/DappViewController.swift @@ -9,35 +9,35 @@ import UIKit import WebKit import JavaScriptCore -import Toast_Swift +/// DApp Home class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, ErrorOverlayPresentable { - private var webView = WKWebView() - private var mainUrl: URL? - - override var preferredStatusBarStyle: UIStatusBarStyle { - return .default + private let webView = WKWebView(frame: .zero) + private var mainUrl = URL(string: "https://dapp.cryptape.com")! + private var customUserAgent: String { + let infoDictionary = Bundle.main.infoDictionary! + let majorVersion = infoDictionary["CFBundleShortVersionString"]! + return "Neuron(Platform=iOS&AppVersion=\(String(describing: majorVersion))" } override func viewDidLoad() { super.viewDidLoad() - didAddSubLayout() + + addWebView() + layoutWebView() errorOverlayRefreshBlock = { [weak self] () in - self?.removeOverlay() - guard let url = self?.mainUrl else { return } - self?.webView.load(URLRequest(url: url)) + guard let self = self else { + return + } + self.removeOverlay() + self.webView.load(URLRequest(url: self.mainUrl)) } - let url = URL(string: "https://dapp.cryptape.com")! - let request = URLRequest(url: url) - webView.load(request) - mainUrl = url + webView.load(URLRequest(url: mainUrl)) } - func didAddSubLayout() { - webView = WKWebView(frame: .zero) - + private func addWebView() { var js = "" if let path = Bundle.main.path(forResource: "dappOpration", ofType: "js") { do { @@ -45,27 +45,26 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, } catch { } } let userScript = WKUserScript(source: js, injectionTime: .atDocumentStart, forMainFrameOnly: true) - let infoDictionary = Bundle.main.infoDictionary! - let majorVersion = infoDictionary["CFBundleShortVersionString"] - webView.customUserAgent = "Neuron(Platform=iOS&AppVersion=\(String(describing: majorVersion!))" + webView.customUserAgent = customUserAgent webView.configuration.userContentController.addUserScript(userScript) webView.configuration.preferences.javaScriptEnabled = true webView.configuration.userContentController.add(self, name: "pushSearchView") webView.scrollView.showsHorizontalScrollIndicator = false webView.scrollView.showsVerticalScrollIndicator = false - if #available(iOS 11.0, *) { - webView.scrollView.contentInsetAdjustmentBehavior = .never - } else { - // Fallback on earlier versions - } + webView.scrollView.delegate = self webView.navigationDelegate = self webView.uiDelegate = self + view.addSubview(webView) } - override func viewDidLayoutSubviews() { - self.webView.frame = safeAreaFrame + private func layoutWebView() { + webView.translatesAutoresizingMaskIntoConstraints = false + webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true + webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true + webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true } func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { From 4da85e11038bf0caaeddcd4575a424538542bc1c Mon Sep 17 00:00:00 2001 From: cezres Date: Wed, 21 Nov 2018 13:49:00 +0800 Subject: [PATCH 129/315] Modify wallet qr code page --- Neuron/Sections/Wallet/Home/Wallet.storyboard | 115 +++++++++++++++++- .../Home/WalletQRCodeViewController.swift | 46 +++++++ 2 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 Neuron/Sections/Wallet/Home/WalletQRCodeViewController.swift diff --git a/Neuron/Sections/Wallet/Home/Wallet.storyboard b/Neuron/Sections/Wallet/Home/Wallet.storyboard index 7547b852..2ccbc7c9 100644 --- a/Neuron/Sections/Wallet/Home/Wallet.storyboard +++ b/Neuron/Sections/Wallet/Home/Wallet.storyboard @@ -603,6 +603,7 @@ + @@ -1137,7 +1138,7 @@ - + @@ -1145,7 +1146,7 @@ - + @@ -1153,22 +1154,120 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1179,10 +1278,16 @@ + + + + + + - + diff --git a/Neuron/Sections/Wallet/Home/WalletQRCodeViewController.swift b/Neuron/Sections/Wallet/Home/WalletQRCodeViewController.swift new file mode 100644 index 00000000..bb7c6423 --- /dev/null +++ b/Neuron/Sections/Wallet/Home/WalletQRCodeViewController.swift @@ -0,0 +1,46 @@ +// +// WalletQRCodeViewController.swift +// Neuron +// +// Created by 晨风 on 2018/11/21. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import EFQRCode + +class WalletQRCodeViewController: UIViewController { + @IBOutlet weak var iconView: UIImageView! + @IBOutlet weak var nameLabel: UILabel! + @IBOutlet weak var qrCodeView: UIImageView! + @IBOutlet weak var addressLabel: UILabel! + override func viewDidLoad() { + super.viewDidLoad() + let appModel = WalletRealmTool.getCurrentAppModel() + iconView.image = UIImage(data: appModel.currentWallet!.iconData!) + nameLabel.text = appModel.currentWallet!.name + addressLabel.text = appModel.currentWallet!.address + let walletAddress = appModel.currentWallet!.address + DispatchQueue.global().async { + let imagea = EFQRCode.generate(content: walletAddress) + DispatchQueue.main.async { + self.qrCodeView.image = UIImage(cgImage: imagea!) + } + } + } + + @IBAction func copyAddress(_ sender: Any) { + UIPasteboard.general.string = WalletRealmTool.getCurrentAppModel().currentWallet?.address + Toast.showToast(text: "地址已经复制到粘贴板") + } + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} From 045c50876014fabba0a0aa8eaff30477c8fee57f Mon Sep 17 00:00:00 2001 From: cezres Date: Wed, 21 Nov 2018 14:12:18 +0800 Subject: [PATCH 130/315] Configure token cell for highlighted state --- Neuron/Sections/Wallet/Home/Wallet.storyboard | 21 ++++++++++++++----- .../Wallet/Home/WalletViewController.swift | 12 +++++------ .../TableViewCell/TokenTableViewCell.swift | 18 ++++++++++++++++ Neuron/Services/Common/Token.swift | 3 +++ 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/Neuron/Sections/Wallet/Home/Wallet.storyboard b/Neuron/Sections/Wallet/Home/Wallet.storyboard index 2ccbc7c9..7c5c1b13 100644 --- a/Neuron/Sections/Wallet/Home/Wallet.storyboard +++ b/Neuron/Sections/Wallet/Home/Wallet.storyboard @@ -602,7 +602,6 @@ - @@ -837,6 +836,7 @@ + @@ -934,6 +934,7 @@ + @@ -1029,6 +1030,7 @@ + @@ -1058,6 +1060,7 @@ + @@ -1138,7 +1141,7 @@ - + @@ -1146,7 +1149,7 @@ - + @@ -1154,7 +1157,15 @@ - + + + + + + + + + @@ -1287,7 +1298,7 @@ - + diff --git a/Neuron/Sections/Wallet/Home/WalletViewController.swift b/Neuron/Sections/Wallet/Home/WalletViewController.swift index 23b25310..11baf7d2 100644 --- a/Neuron/Sections/Wallet/Home/WalletViewController.swift +++ b/Neuron/Sections/Wallet/Home/WalletViewController.swift @@ -23,7 +23,6 @@ class WalletViewController: UIViewController { // private var observers = [NSKeyValueObservation]() override func viewDidLoad() { super.viewDidLoad() - // Do any additional setup after loading the view. let refresh = UIRefreshControl() refresh.addTarget(self, action: #selector(WalletViewController.refresh), for: .valueChanged) tableView.refreshControl = refresh @@ -38,13 +37,16 @@ class WalletViewController: UIViewController { self?.navigationItem.rightBarButtonItem = self?.addWalletBarButton } } - // observers.append(presenter.observe(\.currency, options: [.initial]) { (_, _) in // }) } // MARK: - Navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if segue.identifier == "transaction" { + let controller = segue.destination as! TransactionHistoryViewController + controller.tokenModel = sender as? TokenModel + } } // MARK: - Actions @@ -52,8 +54,6 @@ class WalletViewController: UIViewController { guard !presenter.refreshing else { return } presenter.refreshAmount() } - @IBAction func walletQRCode(_ sender: Any) { - } @IBAction func transaction(_ sender: Any) { } } @@ -128,12 +128,12 @@ extension WalletViewController: UITableViewDataSource, UITableViewDelegate { } func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { - return false + return true } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - +// performSegue(withIdentifier: "transaction", sender: presenter.tokens[indexPath.row].tokenModel) } func scrollViewDidScroll(_ scrollView: UIScrollView) { diff --git a/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift b/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift index 5761509a..46a0c784 100644 --- a/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift +++ b/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift @@ -31,4 +31,22 @@ class TokenTableViewCell: UITableViewCell { } } } + + @IBOutlet weak var ctxView: UIView! + private var overlayView: UIView? + + override func setSelected(_ selected: Bool, animated: Bool) { + } + + override func setHighlighted(_ highlighted: Bool, animated: Bool) { + if highlighted { + overlayView = UIView(frame: ctxView.bounds) + overlayView?.backgroundColor = UIColor.black + overlayView?.alpha = 0.4 + ctxView.addSubview(overlayView!) + } else { + overlayView?.removeFromSuperview() + } + } + } diff --git a/Neuron/Services/Common/Token.swift b/Neuron/Services/Common/Token.swift index f9498927..ebbd2fe2 100644 --- a/Neuron/Services/Common/Token.swift +++ b/Neuron/Services/Common/Token.swift @@ -29,7 +29,10 @@ class Token { var balance: Double? var price: Double? + let tokenModel: TokenModel + init(_ token: TokenModel) { + tokenModel = token name = token.name iconUrl = token.iconUrl address = token.address From 214327a9ee80c9507c479a31b29c771ae86f4e7b Mon Sep 17 00:00:00 2001 From: cezres Date: Wed, 21 Nov 2018 14:12:30 +0800 Subject: [PATCH 131/315] Update project.pbxproj --- Neuron.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index e15f9590..52ef9b3f 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -71,6 +71,7 @@ 6E876843218023520032EBCE /* CoinMarketCap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E876842218023520032EBCE /* CoinMarketCap.swift */; }; 6E876845218031670032EBCE /* UInt+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E876844218031670032EBCE /* UInt+Extension.swift */; }; 6E8962032194575B00D2C0AD /* DoubleExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8962022194575B00D2C0AD /* DoubleExtensionTests.swift */; }; + 6E8D77F021A5264F00EA7674 /* WalletQRCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8D77EF21A5264F00EA7674 /* WalletQRCodeViewController.swift */; }; 6E90D06921704C1D00B98363 /* SensorsAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E90D06821704C1D00B98363 /* SensorsAnalytics.swift */; }; 6E9B947B21A4080300D67357 /* DesignableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B947A21A4080300D67357 /* DesignableButton.swift */; }; 6E9B947D21A4083100D67357 /* DesignableGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B947C21A4083100D67357 /* DesignableGradientView.swift */; }; @@ -287,6 +288,7 @@ 6E876842218023520032EBCE /* CoinMarketCap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinMarketCap.swift; sourceTree = ""; }; 6E876844218031670032EBCE /* UInt+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt+Extension.swift"; sourceTree = ""; }; 6E8962022194575B00D2C0AD /* DoubleExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoubleExtensionTests.swift; sourceTree = ""; }; + 6E8D77EF21A5264F00EA7674 /* WalletQRCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletQRCodeViewController.swift; sourceTree = ""; }; 6E90D06821704C1D00B98363 /* SensorsAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorsAnalytics.swift; sourceTree = ""; }; 6E9B947A21A4080300D67357 /* DesignableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignableButton.swift; sourceTree = ""; }; 6E9B947C21A4083100D67357 /* DesignableGradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignableGradientView.swift; sourceTree = ""; }; @@ -940,6 +942,7 @@ 89D84CE2214790B5006B0287 /* NFTDetail.storyboard */, 1A207DCE2137B3F7008DC306 /* RequestPaymentViewController.swift */, 1A207DD02137B412008DC306 /* RequestPayment.storyboard */, + 6E8D77EF21A5264F00EA7674 /* WalletQRCodeViewController.swift */, ); path = Home; sourceTree = ""; @@ -1408,6 +1411,7 @@ 6EABFB10219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift in Sources */, 6EF8F40C2175CAD9004B7587 /* UIControl+Extension.swift in Sources */, 898CA98320EA0F210059ECA3 /* TokenModel.swift in Sources */, + 6E8D77F021A5264F00EA7674 /* WalletQRCodeViewController.swift in Sources */, 8989E2D32188440D008A1BDE /* SwitchNetworkTableViewCell.swift in Sources */, 898A1A1720B6BE6E00ECB465 /* ChangePasswordController.swift in Sources */, 6EABFB0D21991CB300305ED5 /* DAppNativeMessageHandler.swift in Sources */, From f0d6b1fe44574895c83f352664b683bdfda53392 Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 21 Nov 2018 15:26:38 +0900 Subject: [PATCH 132/315] Fix dapp browser view layout and navigation behavior --- .../UIKit/UIViewController+Extension.swift | 19 ------ .../Dapp/WebView/BrowserViewController.swift | 67 ++++++++++--------- .../Dapp/WebView/DAppBrowser.storyboard | 19 +++--- 3 files changed, 46 insertions(+), 59 deletions(-) delete mode 100644 Neuron/Extensions/UIKit/UIViewController+Extension.swift diff --git a/Neuron/Extensions/UIKit/UIViewController+Extension.swift b/Neuron/Extensions/UIKit/UIViewController+Extension.swift deleted file mode 100644 index 4c694108..00000000 --- a/Neuron/Extensions/UIKit/UIViewController+Extension.swift +++ /dev/null @@ -1,19 +0,0 @@ -// -// UIViewController+Extension.swift -// Neuron -// -// Created by XiaoLu on 2018/11/20. -// Copyright © 2018 Cryptape. All rights reserved. -// - -import UIKit - -extension UIViewController { - var safeAreaFrame: CGRect { - if #available(iOS 11.0, *) { - return view.safeAreaLayoutGuide.layoutFrame - } else { - return view.frame - } - } -} diff --git a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift index e5e4b6d9..abb0befc 100644 --- a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift @@ -10,17 +10,16 @@ import UIKit import WebKit class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipeBackable { - @IBOutlet weak var directionView: UIView! - @IBOutlet weak var backButton: UIButton! - @IBOutlet weak var forwardButton: UIButton! + @IBOutlet private weak var directionView: UIView! + @IBOutlet private weak var backButton: UIButton! + @IBOutlet private weak var forwardButton: UIButton! + @IBOutlet private weak var directionViewHeightConstraint: NSLayoutConstraint! + var requestUrlStr = "" var mainUrl: URL? - var webViewProgressObservation: NSKeyValueObservation! + private var observations: [NSKeyValueObservation] = [] lazy var webView: WKWebView = { - let webView = WKWebView( - frame: .zero, - configuration: self.config - ) + let webView = WKWebView(frame: .zero, configuration: self.config) let infoDictionary = Bundle.main.infoDictionary! let majorVersion = infoDictionary["CFBundleShortVersionString"] let customUserAgent = "Neuron(Platform=iOS&AppVersion=\(String(describing: majorVersion!))" @@ -39,7 +38,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe lazy private var progressView: UIProgressView = { let progressView = UIProgressView(frame: CGRect(x: 0, y: 0, width: ScreenSize.width, height: 2)) progressView.tintColor = AppColor.themeColor - progressView.trackTintColor = UIColor.white + progressView.trackTintColor = .white return progressView }() @@ -49,21 +48,20 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe return config }() - override func viewDidLayoutSubviews() { - self.webView.frame = safeAreaFrame - } - override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - navigationController?.setNavigationBarHidden(false, animated: true) navigationController?.navigationBar.isTranslucent = false } override func viewDidLoad() { super.viewDidLoad() + view.addSubview(webView) + layoutWebView() view.addSubview(progressView) view.bringSubviewToFront(directionView) + directionViewHeightConstraint.constant = 0 + requestUrlStr = requestUrlStr.trimmingCharacters(in: .whitespaces) mainUrl = URL(string: getRequestStr(requestStr: requestUrlStr)) if let url = mainUrl { @@ -83,8 +81,10 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe let gestureRecognizer = fixSwipeBack() webView.addGestureRecognizer(gestureRecognizer) - webViewProgressObservation = webView.observe(\.estimatedProgress) { [weak self](webView, _) in - guard let self = self else { return } + observations.append(webView.observe(\.estimatedProgress) { [weak self] (webView, _) in + guard let self = self else { + return + } self.progressView.alpha = 1.0 self.progressView.setProgress(Float(webView.estimatedProgress), animated: true) if webView.estimatedProgress >= 1.0 { @@ -94,7 +94,10 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe self.progressView.setProgress(0.0, animated: false) }) } - } + }) + observations.append(webView.observe(\.canGoBack) { [weak self] (_, _) in + self?.updateNavigationButtons() + }) } func getRequestStr(requestStr: String) -> String { @@ -122,17 +125,18 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe webView.evaluateJavaScript(script, completionHandler: nil) } - func adjustmentDirectionButton() { - if webView.canGoBack { - if directionView.isHidden { - directionView.isHidden = false - webView.frame = CGRect(x: safeAreaFrame.origin.x, y: safeAreaFrame.origin.y, width: safeAreaFrame.size.width, height: safeAreaFrame.size.height - 50) - } - backButton.isSelected = true - } else { - backButton.isSelected = false - } - forwardButton.isSelected = webView.canGoForward + private func updateNavigationButtons() { + directionViewHeightConstraint.constant = webView.canGoBack ? 50 : 0 + backButton.isEnabled = webView.canGoBack + forwardButton.isEnabled = webView.canGoForward + } + + private func layoutWebView() { + webView.translatesAutoresizingMaskIntoConstraints = false + webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true + webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true + webView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true + webView.bottomAnchor.constraint(equalTo: directionView.topAnchor).isActive = true } @IBAction func backButtonAction(_ sender: UIButton) { @@ -144,7 +148,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe } deinit { - webViewProgressObservation.invalidate() + observations.forEach { $0.invalidate() } } } @@ -250,15 +254,16 @@ extension BrowserViewController { extension BrowserViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { title = webView.title - adjustmentDirectionButton() + updateNavigationButtons() + let relJs = "document.querySelector('head').querySelector('link[rel=manifest]').href;" - let refJs = "document.querySelector('head').querySelector('link[ref=manifest]').href;" webView.evaluateJavaScript(relJs) { (manifest, _) in guard let link = manifest else { return } DAppAction().dealWithManifestJson(with: link as! String) } + let refJs = "document.querySelector('head').querySelector('link[ref=manifest]').href;" webView.evaluateJavaScript(refJs) { (manifest, _) in guard let link = manifest else { return diff --git a/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard b/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard index 3ee25480..f46b0f6e 100644 --- a/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard +++ b/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard @@ -84,27 +84,27 @@ - + + + + + + + + + + + + + + + + + + + + + + diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index c324ada0..28b302a8 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -87,8 +87,10 @@ class SendTransactionViewController: UITableViewController, TransactonSender { @IBOutlet private weak var amountTextField: UITextField! @IBOutlet private weak var gasCostLabel: UILabel! @IBOutlet private weak var addressTextField: UITextField! + @IBOutlet weak var tokenLabel: UILabel! var paramBuilder: TransactionParamBuilder! + var enableSwitchToken = false private var observers = [NSKeyValueObservation]() private lazy var summaryPageItem: TxSummaryPageItem = { @@ -109,6 +111,9 @@ class SendTransactionViewController: UITableViewController, TransactonSender { override func viewDidLoad() { super.viewDidLoad() + if enableSwitchToken && token == nil { + token = WalletRealmTool.getCurrentAppModel().nativeTokenList.first + } paramBuilder = TransactionParamBuilder(token: token) observers.append(paramBuilder.observe(\.txFeeNatural, options: [.initial]) { (_, _) in @@ -118,7 +123,6 @@ class SendTransactionViewController: UITableViewController, TransactonSender { if recipientAddress != nil { paramBuilder.to = recipientAddress } - setupUI() } @@ -126,6 +130,10 @@ class SendTransactionViewController: UITableViewController, TransactonSender { if segue.identifier == "TransactionGasPriceViewController" { let controller = segue.destination as! TransactionGasPriceViewController controller.param = paramBuilder + } else if segue.identifier == "switchToken" { + let controller = segue.destination as! TransactionSwitchTokenViewController + controller.currentToken = token + controller.delegate = self } } @@ -168,6 +176,7 @@ class SendTransactionViewController: UITableViewController, TransactonSender { walletAddressLabel.text = wallet.address tokenBalanceButton.setTitle("\(token.tokenBalance)\(token.symbol)", for: .normal) addressTextField.text = paramBuilder.to + tokenLabel.text = token.symbol updateGasCost() } @@ -297,3 +306,49 @@ extension SendTransactionViewController: QRCodeViewControllerDelegate { addressTextField.text = codeResult } } + +// MARK: - Switch transaction token +extension SendTransactionViewController: TransactionSwitchTokenViewControllerDelegate { + override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + if indexPath.row == 0 { + if !enableSwitchToken { + return 0.0 + } else { + return super.tableView(tableView, heightForRowAt: indexPath) + } + } else { + return super.tableView(tableView, heightForRowAt: indexPath) + } + } + + override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + if indexPath.row == 0 { + if enableSwitchToken { + cell.isHidden = false + } else { + cell.isHidden = true + } + } else { + cell.isHidden = false + } + } + + func switchToken(switchToken: TransactionSwitchTokenViewController, didSwitchToToken token: TokenModel) { + self.token = token + + observers.forEach { (observe) in + observe.invalidate() + } + observers.removeAll() + + paramBuilder = TransactionParamBuilder(token: token) + observers.append(paramBuilder.observe(\.txFeeNatural, options: [.initial]) { (_, _) in + self.updateGasCost() + }) + paramBuilder.from = WalletRealmTool.getCurrentAppModel().currentWallet!.address + if recipientAddress != nil { + paramBuilder.to = recipientAddress + } + setupUI() + } +} diff --git a/Neuron/Sections/Transaction/TransactionSwitchTokenViewController.swift b/Neuron/Sections/Transaction/TransactionSwitchTokenViewController.swift new file mode 100644 index 00000000..bae02f64 --- /dev/null +++ b/Neuron/Sections/Transaction/TransactionSwitchTokenViewController.swift @@ -0,0 +1,92 @@ +// +// SendTransactionSwitchTokenViewController.swift +// Neuron +// +// Created by 晨风 on 2018/11/21. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit + +protocol TransactionSwitchTokenViewControllerDelegate: NSObjectProtocol { + func switchToken(switchToken: TransactionSwitchTokenViewController, didSwitchToToken token: TokenModel) +} + +class TransactionSwitchTokenViewController: UIViewController { + @IBOutlet weak var backgroundView: UIView! + @IBOutlet weak var contentView: UIView! + @IBOutlet weak var tableView: UITableView! + private var tokens = [TokenModel]() + var currentToken: TokenModel! + weak var delegate: TransactionSwitchTokenViewControllerDelegate? + + override func viewDidLoad() { + super.viewDidLoad() + tokens += WalletRealmTool.getCurrentAppModel().nativeTokenList + tokens += WalletRealmTool.getCurrentAppModel().currentWallet!.selectTokenList + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + backgroundView.alpha = 0.0 + contentView.transform = CGAffineTransform(translationX: 0, y: contentView.bounds.size.height) + UIView.animate(withDuration: CATransaction.animationDuration()) { + self.backgroundView.alpha = 1.0 + self.contentView.transform = CGAffineTransform.identity + } + } + + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + dismiss() + } + + @IBAction func dismiss() { + UIView.animate(withDuration: CATransaction.animationDuration(), animations: { + self.backgroundView.alpha = 0.0 + self.contentView.transform = CGAffineTransform(translationX: 0, y: self.contentView.bounds.size.height) + }, completion: { (_) in + self.dismiss(animated: false, completion: nil) + }) + } + + @IBAction func confirm() { + delegate?.switchToken(switchToken: self, didSwitchToToken: currentToken) + dismiss() + } +} + +extension TransactionSwitchTokenViewController: UITableViewDataSource, UITableViewDelegate { + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return tokens.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TransactionSwitchTokenTableViewCell.self)) as! TransactionSwitchTokenTableViewCell + cell.tokenLabel.text = tokens[indexPath.row].symbol + return cell + } + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + guard let cell = cell as? TransactionSwitchTokenTableViewCell else { return } + if tokens[indexPath.row].symbol == currentToken.symbol { + cell.tokenLabel.textColor = UIColor(red: 72/255.0, green: 109/255.0, blue: 255/255.0, alpha: 1.0) + } else { + cell.tokenLabel.textColor = UIColor(red: 36/255.0, green: 43/255.0, blue: 67/255.0, alpha: 1.0) + } + } + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + let oldToken = currentToken + currentToken = tokens[indexPath.row] + if let index = tokens.firstIndex(where: { $0.symbol == oldToken?.symbol }) { + let indexPath = IndexPath(row: index, section: 0) + self.tableView(tableView, willDisplay: tableView.cellForRow(at: indexPath)!, forRowAt: indexPath) + } + self.tableView(tableView, willDisplay: tableView.cellForRow(at: indexPath)!, forRowAt: indexPath) + } +} + +class TransactionSwitchTokenTableViewCell: UITableViewCell { + @IBOutlet weak var tokenLabel: UILabel! +} diff --git a/Neuron/Sections/Wallet/Home/Wallet.storyboard b/Neuron/Sections/Wallet/Home/Wallet.storyboard index ec29ce5b..deb5e453 100644 --- a/Neuron/Sections/Wallet/Home/Wallet.storyboard +++ b/Neuron/Sections/Wallet/Home/Wallet.storyboard @@ -664,7 +664,7 @@ - + @@ -1060,7 +1060,7 @@ - + @@ -1143,7 +1143,7 @@ - + @@ -1151,7 +1151,7 @@ - + @@ -1159,7 +1159,7 @@ - + @@ -1167,7 +1167,15 @@ - + + + + + + + + + diff --git a/Neuron/Sections/Wallet/Home/WalletViewController.swift b/Neuron/Sections/Wallet/Home/WalletViewController.swift index 3f31b934..3073ccf8 100644 --- a/Neuron/Sections/Wallet/Home/WalletViewController.swift +++ b/Neuron/Sections/Wallet/Home/WalletViewController.swift @@ -40,9 +40,12 @@ class WalletViewController: UIViewController { // MARK: - Navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - if segue.identifier == "transaction" { + if segue.identifier == "transactionHistory" { let controller = segue.destination as! TransactionHistoryViewController controller.tokenModel = sender as? TokenModel + } else if segue.identifier == "transaction" { + let controller = segue.destination as! SendTransactionViewController + controller.enableSwitchToken = true } } @@ -51,8 +54,6 @@ class WalletViewController: UIViewController { guard !presenter.refreshing else { return } presenter.refreshBalance() } - @IBAction func transaction(_ sender: Any) { - } } extension WalletViewController: WalletPresenterDelegate { @@ -126,7 +127,7 @@ extension WalletViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - performSegue(withIdentifier: "transaction", sender: presenter.tokens[indexPath.row].tokenModel) + performSegue(withIdentifier: "transactionHistory", sender: presenter.tokens[indexPath.row].tokenModel) } func scrollViewDidScroll(_ scrollView: UIScrollView) { From c739a6129831ec506f338de2d8f0b5a11abe8fc2 Mon Sep 17 00:00:00 2001 From: cezres Date: Wed, 21 Nov 2018 17:08:15 +0800 Subject: [PATCH 138/315] Modify tokenBalance type --- Neuron/Models/TokenModel.swift | 2 +- .../SendTransactionViewController.swift | 2 +- .../Transaction/TransactionParamBuilder.swift | 2 +- .../Wallet/Home/TokensViewController.swift | 42 +++++++++---------- Neuron/Services/Common/Token.swift | 8 +++- 5 files changed, 30 insertions(+), 26 deletions(-) diff --git a/Neuron/Models/TokenModel.swift b/Neuron/Models/TokenModel.swift index 743cdf98..5ee58a3e 100644 --- a/Neuron/Models/TokenModel.swift +++ b/Neuron/Models/TokenModel.swift @@ -30,7 +30,7 @@ class TokenModel: Object, Decodable { // defaults false, eth and RPC "getMateData" is true. @objc dynamic var isNativeToken = false // TODO: AppChain ERC20 should not be marked as native token. - var tokenBalance = "0" // TODO: Should persist balance, or store them globally. + var tokenBalance = 0.0 // TODO: Should persist balance, or store them globally. var currencyAmount = "0" override class func primaryKey() -> String? { diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index 28b302a8..246ea413 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -159,7 +159,7 @@ class SendTransactionViewController: UITableViewController, TransactonSender { @IBAction func transactionAvailableBalance() { // TODO: FIXME: erc20 token requires ETH balance for tx fee - let amount = Double(token.tokenBalance)! - paramBuilder.txFeeNatural + let amount = token.tokenBalance - paramBuilder.txFeeNatural amountTextField.text = "\(amount)" paramBuilder.value = amount.toAmount(token.decimals) guard paramBuilder.hasSufficientBalance else { diff --git a/Neuron/Sections/Transaction/TransactionParamBuilder.swift b/Neuron/Sections/Transaction/TransactionParamBuilder.swift index 457ea5d8..0e4d2990 100644 --- a/Neuron/Sections/Transaction/TransactionParamBuilder.swift +++ b/Neuron/Sections/Transaction/TransactionParamBuilder.swift @@ -66,7 +66,7 @@ class TransactionParamBuilder: NSObject { contractAddress = token.address symbol = token.symbol nativeCoinSymbol = token.gasSymbol - tokenBalance = Double(token.tokenBalance)!.toAmount(token.decimals) + tokenBalance = token.tokenBalance.toAmount(token.decimals) super.init() diff --git a/Neuron/Sections/Wallet/Home/TokensViewController.swift b/Neuron/Sections/Wallet/Home/TokensViewController.swift index c62eade4..3785f3ff 100644 --- a/Neuron/Sections/Wallet/Home/TokensViewController.swift +++ b/Neuron/Sections/Wallet/Home/TokensViewController.swift @@ -84,9 +84,7 @@ class TokensViewController: UITableViewController, ErrorOverlayPresentable { currency.getCurrencyPrice(tokenid: tokenId, currencyType: currencyModel.short) { (result) in switch result { case .success(let price): - guard let balance = Double(model.tokenBalance) else { - return - } + let balance = Double(model.tokenBalance) guard balance != 0 else { if currencyTotle == 0 { model.currencyAmount = "" @@ -120,35 +118,35 @@ class TokensViewController: UITableViewController, ErrorOverlayPresentable { if tm.chainId == NativeChainId.ethMainnetChainId { group.enter() EthNativeTokenService.getEthNativeTokenBalance(walletAddress: walletModel.address) {(result) in - switch result { - case .success(let balance): - tm.tokenBalance = balance - case .error: - Toast.showToast(text: "网络错误,请稍后再试.") - } +// switch result { +// case .success(let balance): +// tm.tokenBalance = balance +// case .error: +// Toast.showToast(text: "网络错误,请稍后再试.") +// } group.leave() } } else if tm.chainId != "" && tm.chainId != NativeChainId.ethMainnetChainId { group.enter() NervosNativeTokenService.getNervosNativeTokenBalance(walletAddress: walletModel.address) {(result) in - switch result { - case .success(let balance): - tm.tokenBalance = balance - case .error: - Toast.showToast(text: "网络错误,请稍后再试.") - } +// switch result { +// case .success(let balance): +// tm.tokenBalance = balance +// case .error: +// Toast.showToast(text: "网络错误,请稍后再试.") +// } group.leave() } } else if tm.address.count != 0 { group.enter() ERC20TokenService.getERC20TokenBalance(walletAddress: walletModel.address, contractAddress: tm.address) { (result) in - switch result { - case .success(let erc20Balance): - let balance = Web3.Utils.formatToPrecision(erc20Balance, numberDecimals: tm.decimals, formattingDecimals: 6, fallbackToScientific: false) - tm.tokenBalance = balance! - case .error: - Toast.showToast(text: "网络错误,请稍后再试.") - } +// switch result { +// case .success(let erc20Balance): +// let balance = Web3.Utils.formatToPrecision(erc20Balance, numberDecimals: tm.decimals, formattingDecimals: 6, fallbackToScientific: false) +// tm.tokenBalance = balance! +// case .error: +// Toast.showToast(text: "网络错误,请稍后再试.") +// } group.leave() } } diff --git a/Neuron/Services/Common/Token.swift b/Neuron/Services/Common/Token.swift index d48995fd..6f8b8f17 100644 --- a/Neuron/Services/Common/Token.swift +++ b/Neuron/Services/Common/Token.swift @@ -23,7 +23,13 @@ class Token { var isNativeToken = false var walletAddress = "" - var balance: Double? + var balance: Double? { + didSet { + DispatchQueue.main.async { + self.tokenModel.tokenBalance = self.balance ?? 0.0 + } + } + } var price: Double? let tokenModel: TokenModel From 558351184fa1c3296926f8f41820f1df742498fd Mon Sep 17 00:00:00 2001 From: James Chen Date: Wed, 21 Nov 2018 18:19:10 +0900 Subject: [PATCH 139/315] Standardize asset name * Use asset_name case * Delete unused images --- .../{Setting => DApp Browser}/Contents.json | 0 .../webview_ahead_off.imageset/Contents.json | 0 .../webview_ahead_off@2x.png | Bin .../webview_ahead_off@3x.png | Bin .../webview_ahead_on.imageset/Contents.json | 0 .../webview_ahead_on@2x.png | Bin .../webview_ahead_on@3x.png | Bin .../webview_back_off.imageset/Contents.json | 0 .../webview_back_off@2x.png | Bin .../webview_back_off@3x.png | Bin .../webview_back_on.imageset/Contents.json | 0 .../webview_back_on@2x.png | Bin .../webview_back_on@3x.png | Bin .../Icons/NullLogo.imageset/Contents.json | 23 ------------------ .../Icons/NullLogo.imageset/Group 2.png | Bin 4251 -> 0 bytes .../Icons/NullLogo.imageset/Group 2@2x.png | Bin 9842 -> 0 bytes .../Icons/NullLogo.imageset/Group 2@3x.png | Bin 16450 -> 0 bytes .../Icons/Triangle.imageset/Contents.json | 22 ----------------- .../Icons/Triangle.imageset/Triangle@2x.png | Bin 286 -> 0 bytes .../Icons/Triangle.imageset/Triangle@3x.png | Bin 327 -> 0 bytes .../Icons/closeWebView.imageset/Contents.json | 23 ------------------ .../Icons/closeWebView.imageset/close.png | Bin 247 -> 0 bytes .../Icons/closeWebView.imageset/close@2x.png | Bin 494 -> 0 bytes .../Icons/closeWebView.imageset/close@3x.png | Bin 872 -> 0 bytes .../Icons/copy.imageset/Contents.json | 0 .../\345\244\215\345\210\266.png" | Bin .../\345\244\215\345\210\266@2x.png" | Bin .../\345\244\215\345\210\266@3x.png" | Bin .../Icons/emptyData.imageset/Contents.json | 23 ------------------ .../Icons/emptyData.imageset/emptyData.png | Bin 11720 -> 0 bytes .../Icons/emptyData.imageset/emptyData@2x.png | Bin 34025 -> 0 bytes .../Icons/emptyData.imageset/emptyData@3x.png | Bin 63769 -> 0 bytes .../Contents.json | 0 .../ETH_test.png | Bin .../ETH_test@2x.png | Bin .../ETH_test@3x.png | Bin .../Contents.json | 0 .../\350\255\246\345\221\212.png" | Bin .../Contents.json | 0 .../importWallet@2x.png | Bin .../importWallet@3x.png | Bin .../Contents.json | 0 .../launchIcon@2x.png | Bin .../launchIcon@3x.png | Bin .../Contents.json | 0 .../qrCode.png | Bin .../qrCode@2x.png | Bin .../qrCode@3x.png | Bin .../select_status.imageset/Contents.json | 22 ----------------- .../select_status@2x.png | Bin 1135 -> 0 bytes .../select_status@3x.png | Bin 2034 -> 0 bytes .../Contents.json | 0 .../Oval 3.png | Bin .../Oval 3@2x.png | Bin .../Oval 3@3x.png | Bin .../Icons/tx_success.imageset/Contents.json | 0 .../\346\210\220\345\212\237.png" | Bin .../\346\210\220\345\212\237@2x.png" | Bin .../\346\210\220\345\212\237@3x.png" | Bin .../Contents.json" | 23 ------------------ .../\345\210\227\350\241\250.png" | Bin 282 -> 0 bytes .../\345\210\227\350\241\250@2x.png" | Bin 400 -> 0 bytes .../\345\210\227\350\241\250@3x.png" | Bin 580 -> 0 bytes .../Contents.json" | 23 ------------------ .../\345\217\221\347\216\260.png" | Bin 457 -> 0 bytes .../\345\217\221\347\216\260@2x.png" | Bin 1005 -> 0 bytes .../\345\217\221\347\216\260@3x.png" | Bin 1540 -> 0 bytes .../Contents.json" | 23 ------------------ .../\345\244\261\350\264\245.png" | Bin 842 -> 0 bytes .../\345\244\261\350\264\245@2x.png" | Bin 1686 -> 0 bytes .../\345\244\261\350\264\245@3x.png" | Bin 2717 -> 0 bytes .../Contents.json" | 23 ------------------ .../\346\267\273\345\212\240.png" | Bin 250 -> 0 bytes .../\346\267\273\345\212\240@2x.png" | Bin 401 -> 0 bytes .../\346\267\273\345\212\240@3x.png" | Bin 550 -> 0 bytes .../Contents.json" | 23 ------------------ .../\350\256\276\347\275\256.png" | Bin 913 -> 0 bytes .../\350\256\276\347\275\256@2x.png" | Bin 2162 -> 0 bytes .../\350\256\276\347\275\256@3x.png" | Bin 3391 -> 0 bytes .../Contents.json" | 23 ------------------ ...4\344\272\247\350\256\276\345\244\207.png" | Bin 683 -> 0 bytes ...44\272\247\350\256\276\345\244\207@2x.png" | Bin 1570 -> 0 bytes ...44\272\247\350\256\276\345\244\207@3x.png" | Bin 2309 -> 0 bytes .../Contents.json" | 23 ------------------ .../\350\277\233\350\241\214\344\270\255.png" | Bin 600 -> 0 bytes ...50\277\233\350\241\214\344\270\255@2x.png" | Bin 1168 -> 0 bytes ...50\277\233\350\241\214\344\270\255@3x.png" | Bin 1855 -> 0 bytes .../dappCollection.imageset/Contents.json | 22 ----------------- .../dappCollection@2x.png | Bin 232 -> 0 bytes .../dappCollection@3x.png | Bin 361 -> 0 bytes .../Contents.json | 0 .../nav_addAsset@2x.png | Bin .../nav_addAsset@3x.png | Bin .../Contents.json | 0 .../switchWallet@2x.png | Bin .../switchWallet@3x.png | Bin .../Contents.json | 0 .../walletQRCode@2x.png | Bin .../walletQRCode@3x.png | Bin .../{Wallet => Settings}/Contents.json | 0 .../aboutus.imageset/Contents.json | 0 .../aboutus.imageset/aboutus@2x.png | Bin .../aboutus.imageset/aboutus@3x.png | Bin ...5\272\225\351\200\217\346\230\216 (1).png" | Bin .../cita.imageset}/Contents.json | 0 .../contactus.imageset/Contents.json | 0 .../contactus.imageset/contactus@2x.png | Bin .../contactus.imageset/contactus@3x.png | Bin .../currency.imageset/Contents.json | 0 .../currency.imageset/currency@2x.png | Bin .../currency.imageset/currency@3x.png | Bin .../fingerprint_setup.imageset/Contents.json | 0 .../fingerprint_setup@2x.png | Bin .../fingerprint_setup@3x.png | Bin .../forums.imageset}/Contents.json | 0 .../forums.imageset}/Group 11@2x.png | Bin .../forums.imageset}/Group 11@3x.png | Bin .../infua.imageset/Contents.json | 0 .../infua.imageset/infua@2x.png | Bin .../infua.imageset/infua@3x.png | Bin .../nervos_network.imageset/Contents.json | 0 .../nervos_network@2x.png | Bin .../nervos_network@3x.png | Bin .../open_sea.imageset}/Contents.json | 0 .../open_sea.imageset}/openSea@2x.png | Bin .../open_sea.imageset}/openSea@3x.png | Bin .../1527734402443453.png | Bin .../peck_shield.imageset}/Contents.json | 0 .../service_terms.imageset/Contents.json | 0 .../service_terms@2x.png | Bin .../service_terms@3x.png | Bin .../source_code.imageset/Contents.json | 0 .../source_code.imageset/source_code@2x.png | Bin .../source_code.imageset/source_code@3x.png | Bin Neuron/Base.lproj/LaunchScreen.storyboard | 12 ++++----- Neuron/Base.lproj/Main.storyboard | 6 ++--- .../Authentication/Authentication.storyboard | 8 +++--- .../Dapp/WebView/SearchAppController.swift | 2 +- Neuron/Sections/Settings/Settings.storyboard | 21 ++++++++-------- .../Transaction/TradeDetailsController.xib | 8 +++--- .../Views/TradeTableViewCell.swift | 4 +-- .../Transaction/Views/TradeTableViewCell.xib | 8 +++--- .../Wallet/AddWallet/AddWallet.storyboard | 20 +++++++-------- .../Sections/Wallet/Assets/Assets.storyboard | 4 +-- Neuron/Sections/Wallet/Home/Wallet.storyboard | 14 +++++------ .../TableViewCell/AddAssetTableViewCell.swift | 2 +- .../TableViewCell/AssetTableViewCell.swift | 2 +- .../WalletDetails/WalletManagement.storyboard | 6 ++--- 148 files changed, 59 insertions(+), 354 deletions(-) rename Neuron/Assets.xcassets/{Setting => DApp Browser}/Contents.json (100%) rename Neuron/Assets.xcassets/{TabBar => DApp Browser}/webview_ahead_off.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{TabBar => DApp Browser}/webview_ahead_off.imageset/webview_ahead_off@2x.png (100%) rename Neuron/Assets.xcassets/{TabBar => DApp Browser}/webview_ahead_off.imageset/webview_ahead_off@3x.png (100%) rename Neuron/Assets.xcassets/{TabBar => DApp Browser}/webview_ahead_on.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{TabBar => DApp Browser}/webview_ahead_on.imageset/webview_ahead_on@2x.png (100%) rename Neuron/Assets.xcassets/{TabBar => DApp Browser}/webview_ahead_on.imageset/webview_ahead_on@3x.png (100%) rename Neuron/Assets.xcassets/{TabBar => DApp Browser}/webview_back_off.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{TabBar => DApp Browser}/webview_back_off.imageset/webview_back_off@2x.png (100%) rename Neuron/Assets.xcassets/{TabBar => DApp Browser}/webview_back_off.imageset/webview_back_off@3x.png (100%) rename Neuron/Assets.xcassets/{TabBar => DApp Browser}/webview_back_on.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{TabBar => DApp Browser}/webview_back_on.imageset/webview_back_on@2x.png (100%) rename Neuron/Assets.xcassets/{TabBar => DApp Browser}/webview_back_on.imageset/webview_back_on@3x.png (100%) delete mode 100644 Neuron/Assets.xcassets/Icons/NullLogo.imageset/Contents.json delete mode 100644 Neuron/Assets.xcassets/Icons/NullLogo.imageset/Group 2.png delete mode 100644 Neuron/Assets.xcassets/Icons/NullLogo.imageset/Group 2@2x.png delete mode 100644 Neuron/Assets.xcassets/Icons/NullLogo.imageset/Group 2@3x.png delete mode 100644 Neuron/Assets.xcassets/Icons/Triangle.imageset/Contents.json delete mode 100644 Neuron/Assets.xcassets/Icons/Triangle.imageset/Triangle@2x.png delete mode 100644 Neuron/Assets.xcassets/Icons/Triangle.imageset/Triangle@3x.png delete mode 100644 Neuron/Assets.xcassets/Icons/closeWebView.imageset/Contents.json delete mode 100644 Neuron/Assets.xcassets/Icons/closeWebView.imageset/close.png delete mode 100644 Neuron/Assets.xcassets/Icons/closeWebView.imageset/close@2x.png delete mode 100644 Neuron/Assets.xcassets/Icons/closeWebView.imageset/close@3x.png rename "Neuron/Assets.xcassets/Icons/\345\244\215\345\210\266.imageset/Contents.json" => Neuron/Assets.xcassets/Icons/copy.imageset/Contents.json (100%) rename "Neuron/Assets.xcassets/Icons/\345\244\215\345\210\266.imageset/\345\244\215\345\210\266.png" => "Neuron/Assets.xcassets/Icons/copy.imageset/\345\244\215\345\210\266.png" (100%) rename "Neuron/Assets.xcassets/Icons/\345\244\215\345\210\266.imageset/\345\244\215\345\210\266@2x.png" => "Neuron/Assets.xcassets/Icons/copy.imageset/\345\244\215\345\210\266@2x.png" (100%) rename "Neuron/Assets.xcassets/Icons/\345\244\215\345\210\266.imageset/\345\244\215\345\210\266@3x.png" => "Neuron/Assets.xcassets/Icons/copy.imageset/\345\244\215\345\210\266@3x.png" (100%) delete mode 100644 Neuron/Assets.xcassets/Icons/emptyData.imageset/Contents.json delete mode 100644 Neuron/Assets.xcassets/Icons/emptyData.imageset/emptyData.png delete mode 100644 Neuron/Assets.xcassets/Icons/emptyData.imageset/emptyData@2x.png delete mode 100644 Neuron/Assets.xcassets/Icons/emptyData.imageset/emptyData@3x.png rename Neuron/Assets.xcassets/Icons/{ETH_test.imageset => eth_test.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/Icons/{ETH_test.imageset => eth_test.imageset}/ETH_test.png (100%) rename Neuron/Assets.xcassets/Icons/{ETH_test.imageset => eth_test.imageset}/ETH_test@2x.png (100%) rename Neuron/Assets.xcassets/Icons/{ETH_test.imageset => eth_test.imageset}/ETH_test@3x.png (100%) rename Neuron/Assets.xcassets/{Wallet => Icons}/icon_changepassword_info.imageset/Contents.json (100%) rename "Neuron/Assets.xcassets/Wallet/icon_changepassword_info.imageset/\350\255\246\345\221\212.png" => "Neuron/Assets.xcassets/Icons/icon_changepassword_info.imageset/\350\255\246\345\221\212.png" (100%) rename Neuron/Assets.xcassets/Icons/{importWallet.imageset => import_wallet.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/Icons/{importWallet.imageset => import_wallet.imageset}/importWallet@2x.png (100%) rename Neuron/Assets.xcassets/Icons/{importWallet.imageset => import_wallet.imageset}/importWallet@3x.png (100%) rename Neuron/Assets.xcassets/Icons/{launchIcon.imageset => launch_icon.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/Icons/{launchIcon.imageset => launch_icon.imageset}/launchIcon@2x.png (100%) rename Neuron/Assets.xcassets/Icons/{launchIcon.imageset => launch_icon.imageset}/launchIcon@3x.png (100%) rename Neuron/Assets.xcassets/Icons/{qrCode.imageset => qr_code.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/Icons/{qrCode.imageset => qr_code.imageset}/qrCode.png (100%) rename Neuron/Assets.xcassets/Icons/{qrCode.imageset => qr_code.imageset}/qrCode@2x.png (100%) rename Neuron/Assets.xcassets/Icons/{qrCode.imageset => qr_code.imageset}/qrCode@3x.png (100%) delete mode 100644 Neuron/Assets.xcassets/Icons/select_status.imageset/Contents.json delete mode 100644 Neuron/Assets.xcassets/Icons/select_status.imageset/select_status@2x.png delete mode 100644 Neuron/Assets.xcassets/Icons/select_status.imageset/select_status@3x.png rename Neuron/Assets.xcassets/Icons/{Oval 3.imageset => tx_status.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/Icons/{Oval 3.imageset => tx_status.imageset}/Oval 3.png (100%) rename Neuron/Assets.xcassets/Icons/{Oval 3.imageset => tx_status.imageset}/Oval 3@2x.png (100%) rename Neuron/Assets.xcassets/Icons/{Oval 3.imageset => tx_status.imageset}/Oval 3@3x.png (100%) rename "Neuron/Assets.xcassets/Icons/\346\210\220\345\212\237.imageset/Contents.json" => Neuron/Assets.xcassets/Icons/tx_success.imageset/Contents.json (100%) rename "Neuron/Assets.xcassets/Icons/\346\210\220\345\212\237.imageset/\346\210\220\345\212\237.png" => "Neuron/Assets.xcassets/Icons/tx_success.imageset/\346\210\220\345\212\237.png" (100%) rename "Neuron/Assets.xcassets/Icons/\346\210\220\345\212\237.imageset/\346\210\220\345\212\237@2x.png" => "Neuron/Assets.xcassets/Icons/tx_success.imageset/\346\210\220\345\212\237@2x.png" (100%) rename "Neuron/Assets.xcassets/Icons/\346\210\220\345\212\237.imageset/\346\210\220\345\212\237@3x.png" => "Neuron/Assets.xcassets/Icons/tx_success.imageset/\346\210\220\345\212\237@3x.png" (100%) delete mode 100644 "Neuron/Assets.xcassets/Icons/\345\210\227\350\241\250.imageset/Contents.json" delete mode 100644 "Neuron/Assets.xcassets/Icons/\345\210\227\350\241\250.imageset/\345\210\227\350\241\250.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\345\210\227\350\241\250.imageset/\345\210\227\350\241\250@2x.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\345\210\227\350\241\250.imageset/\345\210\227\350\241\250@3x.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\345\217\221\347\216\260.imageset/Contents.json" delete mode 100644 "Neuron/Assets.xcassets/Icons/\345\217\221\347\216\260.imageset/\345\217\221\347\216\260.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\345\217\221\347\216\260.imageset/\345\217\221\347\216\260@2x.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\345\217\221\347\216\260.imageset/\345\217\221\347\216\260@3x.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\345\244\261\350\264\245.imageset/Contents.json" delete mode 100644 "Neuron/Assets.xcassets/Icons/\345\244\261\350\264\245.imageset/\345\244\261\350\264\245.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\345\244\261\350\264\245.imageset/\345\244\261\350\264\245@2x.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\345\244\261\350\264\245.imageset/\345\244\261\350\264\245@3x.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\346\267\273\345\212\240.imageset/Contents.json" delete mode 100644 "Neuron/Assets.xcassets/Icons/\346\267\273\345\212\240.imageset/\346\267\273\345\212\240.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\346\267\273\345\212\240.imageset/\346\267\273\345\212\240@2x.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\346\267\273\345\212\240.imageset/\346\267\273\345\212\240@3x.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\350\256\276\347\275\256.imageset/Contents.json" delete mode 100644 "Neuron/Assets.xcassets/Icons/\350\256\276\347\275\256.imageset/\350\256\276\347\275\256.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\350\256\276\347\275\256.imageset/\350\256\276\347\275\256@2x.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\350\256\276\347\275\256.imageset/\350\256\276\347\275\256@3x.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\350\265\204\344\272\247\350\256\276\345\244\207.imageset/Contents.json" delete mode 100644 "Neuron/Assets.xcassets/Icons/\350\265\204\344\272\247\350\256\276\345\244\207.imageset/\350\265\204\344\272\247\350\256\276\345\244\207.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\350\265\204\344\272\247\350\256\276\345\244\207.imageset/\350\265\204\344\272\247\350\256\276\345\244\207@2x.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\350\265\204\344\272\247\350\256\276\345\244\207.imageset/\350\265\204\344\272\247\350\256\276\345\244\207@3x.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\350\277\233\350\241\214\344\270\255.imageset/Contents.json" delete mode 100644 "Neuron/Assets.xcassets/Icons/\350\277\233\350\241\214\344\270\255.imageset/\350\277\233\350\241\214\344\270\255.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\350\277\233\350\241\214\344\270\255.imageset/\350\277\233\350\241\214\344\270\255@2x.png" delete mode 100644 "Neuron/Assets.xcassets/Icons/\350\277\233\350\241\214\344\270\255.imageset/\350\277\233\350\241\214\344\270\255@3x.png" delete mode 100644 Neuron/Assets.xcassets/NavigationBar/dappCollection.imageset/Contents.json delete mode 100644 Neuron/Assets.xcassets/NavigationBar/dappCollection.imageset/dappCollection@2x.png delete mode 100644 Neuron/Assets.xcassets/NavigationBar/dappCollection.imageset/dappCollection@3x.png rename Neuron/Assets.xcassets/NavigationBar/{nav_addAsset.imageset => nav_add_asset.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/NavigationBar/{nav_addAsset.imageset => nav_add_asset.imageset}/nav_addAsset@2x.png (100%) rename Neuron/Assets.xcassets/NavigationBar/{nav_addAsset.imageset => nav_add_asset.imageset}/nav_addAsset@3x.png (100%) rename Neuron/Assets.xcassets/NavigationBar/{switchWallet.imageset => nav_switch_wallet.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/NavigationBar/{switchWallet.imageset => nav_switch_wallet.imageset}/switchWallet@2x.png (100%) rename Neuron/Assets.xcassets/NavigationBar/{switchWallet.imageset => nav_switch_wallet.imageset}/switchWallet@3x.png (100%) rename Neuron/Assets.xcassets/NavigationBar/{walletQRCode.imageset => wallet_qr_code.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/NavigationBar/{walletQRCode.imageset => wallet_qr_code.imageset}/walletQRCode@2x.png (100%) rename Neuron/Assets.xcassets/NavigationBar/{walletQRCode.imageset => wallet_qr_code.imageset}/walletQRCode@3x.png (100%) rename Neuron/Assets.xcassets/{Wallet => Settings}/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/aboutus.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/aboutus.imageset/aboutus@2x.png (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/aboutus.imageset/aboutus@3x.png (100%) rename "Neuron/Assets.xcassets/Setting/Cita.imageset/CITA-Logo-1024x1024-\351\200\202\347\224\250\347\201\260\345\272\225\351\200\217\346\230\216 (1).png" => "Neuron/Assets.xcassets/Settings/cita.imageset/CITA-Logo-1024x1024-\351\200\202\347\224\250\347\201\260\345\272\225\351\200\217\346\230\216 (1).png" (100%) rename Neuron/Assets.xcassets/{Setting/Cita.imageset => Settings/cita.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/contactus.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/contactus.imageset/contactus@2x.png (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/contactus.imageset/contactus@3x.png (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/currency.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/currency.imageset/currency@2x.png (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/currency.imageset/currency@3x.png (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/fingerprint_setup.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/fingerprint_setup.imageset/fingerprint_setup@2x.png (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/fingerprint_setup.imageset/fingerprint_setup@3x.png (100%) rename Neuron/Assets.xcassets/{Setting/Forums.imageset => Settings/forums.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/{Setting/Forums.imageset => Settings/forums.imageset}/Group 11@2x.png (100%) rename Neuron/Assets.xcassets/{Setting/Forums.imageset => Settings/forums.imageset}/Group 11@3x.png (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/infua.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/infua.imageset/infua@2x.png (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/infua.imageset/infua@3x.png (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/nervos_network.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/nervos_network.imageset/nervos_network@2x.png (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/nervos_network.imageset/nervos_network@3x.png (100%) rename Neuron/Assets.xcassets/{Icons/openSea.imageset => Settings/open_sea.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons/openSea.imageset => Settings/open_sea.imageset}/openSea@2x.png (100%) rename Neuron/Assets.xcassets/{Icons/openSea.imageset => Settings/open_sea.imageset}/openSea@3x.png (100%) rename Neuron/Assets.xcassets/{Setting/PeckShield.imageset => Settings/peck_shield.imageset}/1527734402443453.png (100%) rename Neuron/Assets.xcassets/{Setting/PeckShield.imageset => Settings/peck_shield.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/service_terms.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/service_terms.imageset/service_terms@2x.png (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/service_terms.imageset/service_terms@3x.png (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/source_code.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/source_code.imageset/source_code@2x.png (100%) rename Neuron/Assets.xcassets/{Icons => Settings}/source_code.imageset/source_code@3x.png (100%) diff --git a/Neuron/Assets.xcassets/Setting/Contents.json b/Neuron/Assets.xcassets/DApp Browser/Contents.json similarity index 100% rename from Neuron/Assets.xcassets/Setting/Contents.json rename to Neuron/Assets.xcassets/DApp Browser/Contents.json diff --git a/Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/Contents.json b/Neuron/Assets.xcassets/DApp Browser/webview_ahead_off.imageset/Contents.json similarity index 100% rename from Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/Contents.json rename to Neuron/Assets.xcassets/DApp Browser/webview_ahead_off.imageset/Contents.json diff --git a/Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/webview_ahead_off@2x.png b/Neuron/Assets.xcassets/DApp Browser/webview_ahead_off.imageset/webview_ahead_off@2x.png similarity index 100% rename from Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/webview_ahead_off@2x.png rename to Neuron/Assets.xcassets/DApp Browser/webview_ahead_off.imageset/webview_ahead_off@2x.png diff --git a/Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/webview_ahead_off@3x.png b/Neuron/Assets.xcassets/DApp Browser/webview_ahead_off.imageset/webview_ahead_off@3x.png similarity index 100% rename from Neuron/Assets.xcassets/TabBar/webview_ahead_off.imageset/webview_ahead_off@3x.png rename to Neuron/Assets.xcassets/DApp Browser/webview_ahead_off.imageset/webview_ahead_off@3x.png diff --git a/Neuron/Assets.xcassets/TabBar/webview_ahead_on.imageset/Contents.json b/Neuron/Assets.xcassets/DApp Browser/webview_ahead_on.imageset/Contents.json similarity index 100% rename from Neuron/Assets.xcassets/TabBar/webview_ahead_on.imageset/Contents.json rename to Neuron/Assets.xcassets/DApp Browser/webview_ahead_on.imageset/Contents.json diff --git a/Neuron/Assets.xcassets/TabBar/webview_ahead_on.imageset/webview_ahead_on@2x.png b/Neuron/Assets.xcassets/DApp Browser/webview_ahead_on.imageset/webview_ahead_on@2x.png similarity index 100% rename from Neuron/Assets.xcassets/TabBar/webview_ahead_on.imageset/webview_ahead_on@2x.png rename to Neuron/Assets.xcassets/DApp Browser/webview_ahead_on.imageset/webview_ahead_on@2x.png diff --git a/Neuron/Assets.xcassets/TabBar/webview_ahead_on.imageset/webview_ahead_on@3x.png b/Neuron/Assets.xcassets/DApp Browser/webview_ahead_on.imageset/webview_ahead_on@3x.png similarity index 100% rename from Neuron/Assets.xcassets/TabBar/webview_ahead_on.imageset/webview_ahead_on@3x.png rename to Neuron/Assets.xcassets/DApp Browser/webview_ahead_on.imageset/webview_ahead_on@3x.png diff --git a/Neuron/Assets.xcassets/TabBar/webview_back_off.imageset/Contents.json b/Neuron/Assets.xcassets/DApp Browser/webview_back_off.imageset/Contents.json similarity index 100% rename from Neuron/Assets.xcassets/TabBar/webview_back_off.imageset/Contents.json rename to Neuron/Assets.xcassets/DApp Browser/webview_back_off.imageset/Contents.json diff --git a/Neuron/Assets.xcassets/TabBar/webview_back_off.imageset/webview_back_off@2x.png b/Neuron/Assets.xcassets/DApp Browser/webview_back_off.imageset/webview_back_off@2x.png similarity index 100% rename from Neuron/Assets.xcassets/TabBar/webview_back_off.imageset/webview_back_off@2x.png rename to Neuron/Assets.xcassets/DApp Browser/webview_back_off.imageset/webview_back_off@2x.png diff --git a/Neuron/Assets.xcassets/TabBar/webview_back_off.imageset/webview_back_off@3x.png b/Neuron/Assets.xcassets/DApp Browser/webview_back_off.imageset/webview_back_off@3x.png similarity index 100% rename from Neuron/Assets.xcassets/TabBar/webview_back_off.imageset/webview_back_off@3x.png rename to Neuron/Assets.xcassets/DApp Browser/webview_back_off.imageset/webview_back_off@3x.png diff --git a/Neuron/Assets.xcassets/TabBar/webview_back_on.imageset/Contents.json b/Neuron/Assets.xcassets/DApp Browser/webview_back_on.imageset/Contents.json similarity index 100% rename from Neuron/Assets.xcassets/TabBar/webview_back_on.imageset/Contents.json rename to Neuron/Assets.xcassets/DApp Browser/webview_back_on.imageset/Contents.json diff --git a/Neuron/Assets.xcassets/TabBar/webview_back_on.imageset/webview_back_on@2x.png b/Neuron/Assets.xcassets/DApp Browser/webview_back_on.imageset/webview_back_on@2x.png similarity index 100% rename from Neuron/Assets.xcassets/TabBar/webview_back_on.imageset/webview_back_on@2x.png rename to Neuron/Assets.xcassets/DApp Browser/webview_back_on.imageset/webview_back_on@2x.png diff --git a/Neuron/Assets.xcassets/TabBar/webview_back_on.imageset/webview_back_on@3x.png b/Neuron/Assets.xcassets/DApp Browser/webview_back_on.imageset/webview_back_on@3x.png similarity index 100% rename from Neuron/Assets.xcassets/TabBar/webview_back_on.imageset/webview_back_on@3x.png rename to Neuron/Assets.xcassets/DApp Browser/webview_back_on.imageset/webview_back_on@3x.png diff --git a/Neuron/Assets.xcassets/Icons/NullLogo.imageset/Contents.json b/Neuron/Assets.xcassets/Icons/NullLogo.imageset/Contents.json deleted file mode 100644 index 45fd403b..00000000 --- a/Neuron/Assets.xcassets/Icons/NullLogo.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Group 2.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "Group 2@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "Group 2@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Icons/NullLogo.imageset/Group 2.png b/Neuron/Assets.xcassets/Icons/NullLogo.imageset/Group 2.png deleted file mode 100644 index ded1a603bb365d7c4c68856a19ee982632e004a6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4251 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 diff --git a/Neuron/Assets.xcassets/Icons/NullLogo.imageset/Group 2@2x.png b/Neuron/Assets.xcassets/Icons/NullLogo.imageset/Group 2@2x.png deleted file mode 100644 index 27974b101ad9256aaf38f4a1a826352fbbdf5349..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9842 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 Y4`>|d=@Mdn@&Et;07*qoM6N<$f`WuATmS$7 diff --git a/Neuron/Assets.xcassets/Icons/NullLogo.imageset/Group 2@3x.png b/Neuron/Assets.xcassets/Icons/NullLogo.imageset/Group 2@3x.png deleted file mode 100644 index 5c47ef7f649b3d3047dcd8e385b9b931e998dbab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16450 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 diff --git a/Neuron/Assets.xcassets/Icons/Triangle.imageset/Triangle@3x.png b/Neuron/Assets.xcassets/Icons/Triangle.imageset/Triangle@3x.png deleted file mode 100644 index 665fbe0e3b5dc4b935faa7ae9c8bfbc592e47800..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 327 zcmV-N0l5B&P)Px$0ZBwbR5%f}l)DXsFcd|7Ek!}f5XlPpEWiM1X;RRmNaQ<3q;#ZZ5hkI?3M_y` za*;s%LNLZO*x2u!<_J8VVUT6Q6NVot5XjwnAN&AFD07jz0`d7N*GxbKvWEwozRU>Pr}7g{e*Nyf{=3vHLD zEaSkCLc7WvCF4+Ep})!-E#qKUp?&3P$(n`!D^E+t;j&POH~*!@Kd%cs*|#tZt;t}f ZKp$M+SZ{%r6ZQZA002ovPDHLkV1n)Zi_HK4 diff --git a/Neuron/Assets.xcassets/Icons/closeWebView.imageset/Contents.json b/Neuron/Assets.xcassets/Icons/closeWebView.imageset/Contents.json deleted file mode 100644 index ce0bef5e..00000000 --- a/Neuron/Assets.xcassets/Icons/closeWebView.imageset/Contents.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "close.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "close@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "close@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Icons/closeWebView.imageset/close.png b/Neuron/Assets.xcassets/Icons/closeWebView.imageset/close.png deleted file mode 100644 index 4166e2f67191c3133f9ca20510bb1b0d03124902..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 247 zcmVPx#u}MThR5%f>lmQNaAP7aRb(#)W=cui&Z2wm{b2h%4Y@{MQAj=SAoS_SJ2#vy+ zH+Bw~tWe4cm0|RS9#G0k&;&Ju1cQGLVa$}8QDGIxw+q(+DHR{LvL*;=Wq@UXz3DyP zaRqG0no5xl!;+^Ff!iJaJodCvnT!=%Zu*``zN5@hK>nSuEvrm#t$>`C7yM_rv<3nHPYo`y8u!!+-tSAwcP*!002ovPDHLkV1g|rVV?j1 diff --git a/Neuron/Assets.xcassets/Icons/closeWebView.imageset/close@2x.png b/Neuron/Assets.xcassets/Icons/closeWebView.imageset/close@2x.png deleted file mode 100644 index b5cef49e994f39b65e909add2da186e0ae7b0588..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 494 zcmVPx$s7XXYR9Fekm|H_a3@axl~!RaPGgGeuGX4woz;5-6htba9$n6whVs5`I%YW`K)=bG)?r#RQ1&jg8_6e)Z$`(LKqF#xqufA%>NWApZ9UBhZfmhW`2}bUl zp#n_$jZ?oOgdo!|YGD8m8=7V&<$_*mOBi_iafQW=aC)(&3KiGTAXJ)&={RQZ11l+& k{{t&d4y5m}r#RC41CjSY*X5Cr8~^|S07*qoM6N<$f{i}g&j0`b diff --git a/Neuron/Assets.xcassets/Icons/closeWebView.imageset/close@3x.png b/Neuron/Assets.xcassets/Icons/closeWebView.imageset/close@3x.png deleted file mode 100644 index e24cf60dd3484a8de4a2e28b2ad2793907c19160..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 872 zcmV-u1DE`XP)Px&B1uF+RA>d=T5WFIFboa)d+WO8F3qrE7aMx3eY7X)c8lyWqgt63MM|bpB?8!3 zvP`~5Qk0B(x8rs^9`7OFAs^D%rTh|R#(r2}#^6`T9|*N4$j8Nmi95i02l)-5_80Q) z1Q!LgJ*lP|hygm^N~Z%^xYbehhLH zxw2QJqQE}cgrt&=%SF>8B{ASJirjWZQgKJ0v1rw=-O+}^^&;&6Z9T#mwDj{wN*aP< zN`ZLA(omuAaO}3z5e(cyv{V=pIVPn!G|r~sz0j=ywqgoJAq$E_2DuO~8jQ$@S8!>=1dH2mDmGL@VG9c}2N#x9Oj5+5m6q1D2 zXL4Oz!Ioz=JfY(Tz~cItHd4|vOwM(-*khrEMmq?(FOH2$hS zfUXq-BDAU4RE>|m3GlU-Roq^+c_SsA1?XOKn@%gz?pgXEM{7z;JqjD8=ItmjH=`6^ zrCvmWiioyxNoKys4T3=d8e_@ne%wIeJ_WH^_NYx^hZ_rwb&KEf&Qj2xO4$#QW zbEI+rUA?{-(Oo#=O{%K_u$o$iL#odXB@1A+l1{)@k&=K|hgLz)A36562B^u~tV>&S zO}eDxn)(ccyjf~pUWTID*|PYu+63B~YyjvYm24Wi$3>0}hooO3BYzEYn;a)?o%%3X zD-@xrVmL64oMC84iohoQBV=55KzZ%#7w5e~p0Go;_c?N?xt$c$?&vcK>+CoDU$%@v z+X~tp%L?hiQfpUObe(Sy0IaRR=io3!KHRQZj7eK_wxbONLkfk9876gOb403dv3ij^ z<^dxWGk@f|z2N{8J;eyXYgS?69-PyNf=NU{RCodHeG8afRh8yG=iYkc4T%X7hzSAYEg<*+X-GrUFbFm+qy`-s2j){8 zY_&n?07<3`lhDxE_!y^^7WuIG=3xaL6dT9+>=v{EY3XiWBM(s?LkNZtNT?+Bxc8j- z|F!ly_f+Lp<)(6Ps!nR}|aTzh*uw=Z=I6XzC2 z_#j`zd~>gxvT1Yj#t;5;>EG_I3Cg>xAjk&`jHd;<(=;Aj>-Kk5x3E%mi>eg>1)!-W zZlOb-0x(+>H`T_7x66bqU)`@1yQ5Dzp**9l z?0zzYVT_dkq%j&Uh&9Io_dJqL-8kUhF^EV4Ff2*|6h@Q^K8!NG8{GVA>SBTefXP4t z5IKH?PQ+lY=5p-@CL87Ex6%!dKAUVA7)XwGaa1|+s8YG34XthNKI7S)JOF8TtQTY( zvcO%Brbof)4`u{`z(Rm#5qw`F%7*Q3t^tA}gqYxmUlL)YqJSZp$v_ejJic9W*7Gz} z2M1l{iRUX%zwxF!DoxXQY(M(oQhC;lh{dPAd~viMY`dKTkapXILBY-}P^q{+c_G58|)4^n8tSKI_0K}E2@s1~^B#OOB{oe*7`mffLX{3iT;kRYMoGZ`2p z!l15hE=^e*d88e=$+1A1$*Y8OuWw1;c<8C>t1oRx7Nd0{mfhcApphH`Nn zfMhlcd4VkOk>jHmKKkD1lN}{@NEExT0|5Qm@N@08bVn_U-9FQm&Il!+ zQuC&D98v%htq{_Nc(>CyClLIsG|lT1r4H- zfQN%&TW4G3PCBCG+OWA`G|LgaezE4F=p=1v&UcvFHOG!YOA9P;<=N4ut3DX5?3nHr zJ@aaE=Wkz2I?+vst$zR+RPm8(M4Zy$A}c~e3Hb@wq3gt*3!Kg|TFAPHZQQkM;`Jjt z>;NRQM93$s1+Mu0=*?H&NdKN7A!wLlR0KE#4?nW-(TlFfEsotR?9h=U0D_m}-ntr? z%I>G4q={eQ&W2Hp`Cog|>2JjD@HDL+gPHb$IQy-S)6#v{c16#PA{LrjVF6CjunkVo z$euV*l44CnY)N1ekVS4WHWv1uEmRo=r+EYv6LZuDX72AEP)hT@Lqw`$OWczcuU=cd z{EaAi0SnsO@DrSlwQC!Gwv($-^0U=zD|g@6lfI*lXdt!90+^HO&YW&(6G1wa^AQOE zn>P!0E*ubR^kKX3)S{9^sFHi=%9Eq*%^qfmNW(1J)04IXMEx-OgK1iN&&sao`C%*$ z?W+b|^~D?RO8@w}3!}S-w}*Q3El@%@sz9ww;k;Mrf=4}MI!Q% zmB7Nx@E@$bnR$pvIct9P-gM?OQSu9dXvNa<2kL={>?@X*R+i$@5lNcdwx%cjU`|CS zw$K7x?+_gNVPrxX=qO`{?mdT;Tst=xK;ws-3dSpSi%x9;NG<${ocrld)AE+B)wQtq zZ3NNIIzuFS8o%P%iA(OlH1C+5@rU-^w19!ga-c+jf>qD42PC?=KoAiy9&EC~;Ohdy z*lk<9*!|OPj-;+Qx}MyLPhipl54`SOR9%^uAt>|p zP~LqDh@%1&TpQ{3w?>@l>mPsOrCOA^gf&bN z1}2N1+KydY-bqnx%EVsUK?I@YC{;8Ks<#=w)NK}qDwUtZP&upKuu;**8paglj`3oS8lsxzd07>^V{GsmxvfJ;x-$KGFAQ zI0lYoOiF@~taGMs_|3y@E;+Ik$LmYwwyWptJ8j{A|MGjfzxAb~Hsww9nKPob?Xi15 zrgXpNw65Ib=a2GzvCN>jSEawWD?Pp%xfuxHGg4Qbjx*5=`~n}q9mDTi*;P7nJP&qN zPxV(Y_``Sv`sYzn+Jdod!QKCxr%!cH0c=gXrGGq+u~mJ4@UPR4w{Lp&@~NBF^VKCc z@4INm?f>Pohwk*Gh+WxP>09njM5h&>I5m2_RpZ<1wrQ(La(f!3Z{h6!768D$T_BX7%=#)GJ$>wpIIHoU-M$uO2${`0sRgN4&|Qp>)}a(PnJ){1KTy(U3%73KrN4EPy*j&b*-~ zZR)c^dx2TPAp{Eq3j_-U3j_-^Xo040Hk^D_I^e36CRzR}PS$Ho*@@iNceR{@|6uETA23lBZez5N~gxtaUUv+f9K zo6ox_8F2d@IM*F{!eOp`N{0Z#fH7iU9#jNR#+#!dTs?)W4;T2t_TgI$p90hk7NZs9O`N4teSJG;FxqyPsCVNl6&WN#wT2MUNNW9dH30=S(bLEv$qab*1{oAmvxQ@K^Bn&GzO>|nApw@ z1_O@qGg8sH`C)lX2^$F=o1S*1fh{h^11ZP6??{P8T<}n(9N=)U_yz%%w}6!`A64GD z|GD1`cj=W~&42cPOg0EY<8Fb5fkY7XS1S)QC zWW4lcf=4$XYM|Q{i$nm?l-HkduF@yB${&BqF>cO&vt>Nlnui_bjxqy^??~z_kHj;A zJlG$aH^ZF~z~l`%$+L!TTJ6RYpA}bK3tJsV0ePnwJP9u?m*+fq?w2we*`{=*(V=kCV^kLOQ*+vuJRXgR)XIzZ+5Z@;A&SMX8bLPz!annMO5FnDX%<8bZ66IS% z7bGz6q4e_s6xyzzAZ+`9lp0iw^%(~;%X zNCbp{gboWDKWHPbf<(A=;)Z* zfoQZ?^cYYwVE~6)?My>}Vu+A-<>okQ@_q!v_*Qh|c0@sOf?es=EkVX*kcX52B5d=QsfFJ;YcWERdFnmM;EdvyE z37ykI^ltJ3BGV;6t8csrZ&mNywvGeB<^uk(&JpjxqsIObAMgJJsU#Vl(XX~#ar)5rvgM@i{khXmVY>Uaun7=HH`|Sjf2Ub z0ssfl$?1sl@lGzlG8(}{Iu6}H#M8C#OIvMvM_P@SG6Wgrv@C#({3J(>kJ4!BtzaeV6X z3tRVW&}jX)s%=Cd{rj(_bKro>#KG}p4lZ?!#sDMqe7gr4+ck=LS@H-U=md}O84V8V z

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 eEPw|R(*F;5d&VjMK$epL0000q)qs- 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 diff --git a/Neuron/Assets.xcassets/Icons/emptyData.imageset/emptyData@3x.png b/Neuron/Assets.xcassets/Icons/emptyData.imageset/emptyData@3x.png deleted file mode 100644 index 0e616dd8ee8b94852668d40bc69e78959f2196df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63769 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

gvIR&5!AVb@jKL4PyLIz+q4FblK?C^h9}lRS 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 BDbfG{ diff --git a/Neuron/Assets.xcassets/Icons/select_status.imageset/select_status@3x.png b/Neuron/Assets.xcassets/Icons/select_status.imageset/select_status@3x.png deleted file mode 100644 index 22db40d58ca28c11cad96e53587a9b5810b58d5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2034 zcmVPx+tVu*cRA>d|m`iJ2RTRh1y$y(M9!=5|?EB%MpTZBIP{E04`XzMW)OiQaoC!{> zI1()MqlhCFv1X(oR4NorT)+QW|9#fIZDSMix;LAiz1Cju|6Y5ao72%-??3us>-*2| zbf;vDS1-SP=i=>q|C|!M-FPREZruK0VdK_^rvmeMlrT5m-0V&R=rMSAXmdOpw>P^B zbKYqI9=Xkd`{JB~7GS#E1!y&zPT!i}PA}*<3vo;!&Td}_(2U=I`dV~erUf5oV`T&2 z8GvT}$ItTvdfWgSXmr?{V9@I~=jSJD47fbSJ*Bqkgnp6D@}1Tl_MHW+@3g@p^CqBMzq#41-I(1wtvgKj7=Uz_ z1vHULjxNV~-08I$eUS}!IKhBX-DMq&!Rme&oAn#B+3ATtEd@3Z`iQr)oIp(seio1S za~%ex0U03Fn?T1azZQ3Tb%yS=zK1IS&Tw8`;kC}7o6bzw)9G0O-M)wYvYJ2}DBW#n z<+U+@PT;cwdN|UYRYp=buon);SnE{Q4S-*Z7uo9dll_x5@zMeeN4Q}R#z)%V1Q>&` z^QPDao$mAr1}(q>kw5_k5Me?F6QhDM(p2?&&At8V3GVbuzlU3!3lM;M0}yLt0=0ol zhcL$Z0FC_5UryGi<*whusyrth^+1q#2H+vE3{!6iGVV0vB!T|l?_pIOlYy|VyxS`o z8^Q*}Lw9-3U*C-93bNEw6Wb|+Lxdv+KVhu@?ZjDd<6R1$pAh4EM>6(c^zfFBq5w!|{7@|*3|z-`SPt^EYbU@qWfMabe~leK z=skZAQ@A%lv}{Pz!7xHMy?Gq(lAK3S>!I%n60A>RKPJ6u>&?cAi=sG!T(o_J|dFu)r2U<}kiu#rL7xo+E_s=V!x!@n$U&B>iU zOwaQH5tT zuv#EUbx<~>UY{k7_mzYDoj#K9VQ?@i5Zz-5QRrVk0X`i}0lj!YpogEOa^%RToaMN| zQb5)srl~{d7m-0VJ3z5;eAwjiR}34cU5MMT?LmPa-qM-OMW~QSt6k0o$*osKmZzP; zqLV77X@B{7N`IEZT-L}8mSCt30`!r84+Dfv(B=g7h5$%-g&eEY1x1b++odaUyf5$n zP9MwnusRn2>JT6VLfvboPmEQI-06LS9`0~vZ@bAls-O^tI?=PO6Vlm1MrnXOPdcm6 z;HzniokH6T5e9!`j+G(1bJ6K@`vA?y!(ZM><)$!Yk1VFOijo|)Ol%j_g-K`mEBFft z_PH^*v^t8vLZ%JJd#|9!QSp|GRNRO-vCd>pPGOa2UG1AL6w;w5263S=Y}2n|4h292 z#&$MNBl|cOV)(^9f*!|TLO)If%jB77qB!hS6iPGLn-8(U+qUYac?y<&_?u^~a5W}& z@i=GIW4{;B<1;(mBVrM(29hi$1AtauUBN+4L8d4L>WN9JDSu)~Za)$RPaOQ}kV*Q( ztB&+{-gB2cA4EvTi`-v9Z4jOhJ>Gv0XvvIJmT$unV8& z77Q+Svk#!pUfTU9Yy9o~(c|&B`;z#Ky2AuAPzRL}nMwh+vA=AX(oXB(Su&>M)|@q# z+!N(={$>|OjgQzI@7+M_XX&*w-KV2*_k;v01{#kFfl0pSSoB<~v`A4q_7=86(*QCZ zSGjP2iN~>~?J&{6z{v|wE}q>wEx_O0AMvKX7rxud=8PF1qQXP&Jji60Ns!wHh3&Ah z9aJ`hS*fqqBA)+w$hw|xJ?Q@4>i-j= zbr>TCo$eO8J8`GK7{%M$JBxGW6%2|A;Fta~>Cyt6avZpRJ36dSiF!z@PeGeMk+ z9!RXEW{M0uWw)Kgpjew1#a_-mrEua3AaOIMHYS5rd*w@i6;bj({p~^bA5xBc3$e*e Qvj6}907*qoM6N<$f;gez%K!iX diff --git a/Neuron/Assets.xcassets/Icons/Oval 3.imageset/Contents.json b/Neuron/Assets.xcassets/Icons/tx_status.imageset/Contents.json similarity index 100% rename from Neuron/Assets.xcassets/Icons/Oval 3.imageset/Contents.json rename to Neuron/Assets.xcassets/Icons/tx_status.imageset/Contents.json diff --git a/Neuron/Assets.xcassets/Icons/Oval 3.imageset/Oval 3.png b/Neuron/Assets.xcassets/Icons/tx_status.imageset/Oval 3.png similarity index 100% rename from Neuron/Assets.xcassets/Icons/Oval 3.imageset/Oval 3.png rename to Neuron/Assets.xcassets/Icons/tx_status.imageset/Oval 3.png diff --git a/Neuron/Assets.xcassets/Icons/Oval 3.imageset/Oval 3@2x.png b/Neuron/Assets.xcassets/Icons/tx_status.imageset/Oval 3@2x.png similarity index 100% rename from Neuron/Assets.xcassets/Icons/Oval 3.imageset/Oval 3@2x.png rename to Neuron/Assets.xcassets/Icons/tx_status.imageset/Oval 3@2x.png diff --git a/Neuron/Assets.xcassets/Icons/Oval 3.imageset/Oval 3@3x.png b/Neuron/Assets.xcassets/Icons/tx_status.imageset/Oval 3@3x.png similarity index 100% rename from Neuron/Assets.xcassets/Icons/Oval 3.imageset/Oval 3@3x.png rename to Neuron/Assets.xcassets/Icons/tx_status.imageset/Oval 3@3x.png diff --git "a/Neuron/Assets.xcassets/Icons/\346\210\220\345\212\237.imageset/Contents.json" b/Neuron/Assets.xcassets/Icons/tx_success.imageset/Contents.json similarity index 100% rename from "Neuron/Assets.xcassets/Icons/\346\210\220\345\212\237.imageset/Contents.json" rename to Neuron/Assets.xcassets/Icons/tx_success.imageset/Contents.json diff --git "a/Neuron/Assets.xcassets/Icons/\346\210\220\345\212\237.imageset/\346\210\220\345\212\237.png" "b/Neuron/Assets.xcassets/Icons/tx_success.imageset/\346\210\220\345\212\237.png" similarity index 100% rename from "Neuron/Assets.xcassets/Icons/\346\210\220\345\212\237.imageset/\346\210\220\345\212\237.png" rename to "Neuron/Assets.xcassets/Icons/tx_success.imageset/\346\210\220\345\212\237.png" diff --git "a/Neuron/Assets.xcassets/Icons/\346\210\220\345\212\237.imageset/\346\210\220\345\212\237@2x.png" "b/Neuron/Assets.xcassets/Icons/tx_success.imageset/\346\210\220\345\212\237@2x.png" similarity index 100% rename from "Neuron/Assets.xcassets/Icons/\346\210\220\345\212\237.imageset/\346\210\220\345\212\237@2x.png" rename to "Neuron/Assets.xcassets/Icons/tx_success.imageset/\346\210\220\345\212\237@2x.png" diff --git "a/Neuron/Assets.xcassets/Icons/\346\210\220\345\212\237.imageset/\346\210\220\345\212\237@3x.png" "b/Neuron/Assets.xcassets/Icons/tx_success.imageset/\346\210\220\345\212\237@3x.png" similarity index 100% rename from "Neuron/Assets.xcassets/Icons/\346\210\220\345\212\237.imageset/\346\210\220\345\212\237@3x.png" rename to "Neuron/Assets.xcassets/Icons/tx_success.imageset/\346\210\220\345\212\237@3x.png" diff --git "a/Neuron/Assets.xcassets/Icons/\345\210\227\350\241\250.imageset/Contents.json" "b/Neuron/Assets.xcassets/Icons/\345\210\227\350\241\250.imageset/Contents.json" deleted file mode 100644 index ded78b0d..00000000 --- "a/Neuron/Assets.xcassets/Icons/\345\210\227\350\241\250.imageset/Contents.json" +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "列表.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "列表@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "列表@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git "a/Neuron/Assets.xcassets/Icons/\345\210\227\350\241\250.imageset/\345\210\227\350\241\250.png" "b/Neuron/Assets.xcassets/Icons/\345\210\227\350\241\250.imageset/\345\210\227\350\241\250.png" deleted file mode 100644 index 47958755b748b0bbb802bb9089abc6b3db86cf4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmV+#0pPx#)Ja4^R5%gElsyW`Mbn5Pf`y(yM8rA-ExnD6SFrI&HiD-}?x2NeAtb{3 z4QwD)ejw<;$Jm);Sj#VX06TehKvW^qg3kNrDY9g_`n8Y*uw$pYNrQj zM?E*D3!WacKp0S(fX{K$B2pc#+%_y8|)@A zObS8Lu`e~5dX&*XVW+{WrLFBnD_!OvH`{w5Ihm+%IxD5&`c&}h3?qGODLgPx$N=ZaPR9Fe^SW6B8K@go02`+%cxC3zs+yciz#7)?90ZSx48{@sol$4Iy(4Cn@ zchb75rd7Rp>6xb_NySD=JXw|vv2a8U)itRA% zIKgS#s_=VuLjZfndbPah-4=&UX2_NuPwND*1%m<#kpH)o3aH_Hy#-&+mx=3M6Y^>b zhI*cIhE)$WLp;=H8fNTakBslrltb+^Vayd7hzt}lAnzK;05yt_y$(M1OK8jc4URp$ z9!kqtms+-=L2->*CQN*OR9nlMpJ0!Pqh(tI50000Px${z*hZRA>e5Sv^YwK@iQUsHw$95Cs2$rC?z#_!n$!gp}4I*oprjg`^T&!P-Jd zX(vJoMFjl`ieK@))ghb9T(0nL?gBILvYVORn>X)ZbCYW{+$KwbgmnA;{x*hvPWe4D z#(ThCk|ZCIKTNT24wE*p0;KoCWp3nq0RjzX; zCNbLA@1TCZtKNY#FFsglGYhpyVKylZvv^^KVi%H1?wA}R+C}FzXXML^M@vnnp%y8q zi z$0Eibfg^OKpBRS>`)1)d1eSpGUbxJSd^_NJ6*7Zh3&^rN1X#d`!QTWPfI3-=MCIvz zlA^o3faunKvUliCX_N7wSMDtAm8PAyYjhuqXQRp=Y)pl?B>1Eh~^ zIqttRh_bUj1L}0#vlcg3z!h)>Tme_W6>tR%72qQi-$Aco*y5DmBV(K|B~HRNnz7t z_^5xqMeFn8(NdFXs73w~ctXl+Ks)YxieDJAv+e@VW7f!8i;+K1ti^5O6!-yVg1rEk SAHjJ50000Px$gGod|R45f~lFv&5Q4q&x-mV%^7zNfr3W*?81fh_+MId%8kdArl;z5d_OQ%Ha zARYT7g6NibNr(=C5cu;_hmKteH=$+S-D#fk77xv3=6&bG?CkeFgo%i!jcwE@%TiRz z2-LD2yzSP4FA76h#9|%4SPowyV4f*mHN8x3rTP1M4)q;dNKveW%ZOO`4<>>p0p{Y< z$&zF-n(n7`9iO?@q@aR`-U+DUxWjv0_@4kV;*hf;G#w5Z@?eI3T!J&pu#)YExf>mQ zNBs3w-d*UVWd+js2i0VB@e;^s_-A~n^<85Dgea&UA;pBWLJ0u$;h5%)JiCZ}zYk7% z@}k#AB;m4kO5nQOsfPwIw0augJ|Uc_-Y63_F}nAi)KF{iq^@aNnW=!DBNTZ)FHOSi zmT5coChGu+j8WqKjuD~H#ziJURg+5>t!l71a_93c@EBU#nq})UJi&8J!ffw=Q?XUO z=6k5~mJ<=rd?kd?4u~)|mgUoM?s~QPw!;1Z7E+AHd3;N500000NkvXXu0mjfW9iXy diff --git "a/Neuron/Assets.xcassets/Icons/\345\217\221\347\216\260.imageset/\345\217\221\347\216\260@2x.png" "b/Neuron/Assets.xcassets/Icons/\345\217\221\347\216\260.imageset/\345\217\221\347\216\260@2x.png" deleted file mode 100644 index 1814f3fa9370e8670e0e3a585331a84b91aa95e7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1005 zcmVPx&r%6OXR7ee-RZ(mdWe}eEcW)~R^m;TPt%61pYi+M@7(|pxYI_HfM`B2P=mRkX zBq1Rf6Q6wHg+NFRVnU1|KKbICcLm#24OOG~&|V8QHGxFXa=rHMLhm?k?;pRv++No0 zaV`AWWM;mZ|C`zQXa8ktu)6vdwyBUBAmUzxz06GEYWO1Lj20d%yQSpJ)lGM+_zJ-* z5CA>#%=1F_DX52-xwT$Q4HLRYq%Z4-r8B9KXXk2oRaudsCzd}TN`{ztTQInhEh4j| z<;di4%lH1c|0Rg-%MZeN5uoD_TDvXf+~rUD<*OB6>8= z)*s;TPuv~J>!=;Fn_|eeDmh*|53VoEqR-zf-VGC*!z&&hgu+g2odsz>3) zuaF=g(c~VVO0-S~o!x!&oxlczxUrQ|dz9b{t>^SGkl$zFqlP@%mwm~!!}A%boY2Rp ze!U;_bPT{NikQ96-@}9QtBGXuOy}Ok7s%n?Eyo_;-v94r6Vw&U?GWLE!3{n;Vq?58 zU_QvFqWX+m(q{~$Ew{|%%;Sv<&+t0eV2bGzoL`gC>n->+VrZeBvx;4~F&epTbaSEQ z3;MN(xzd|vzPsYty=cw;2758Zl(A9K4J6rJC-6>@3PXPU)>tHKY_w)TF`t8K`Taqi zNv3Vg^r*#rIOs&;v$Kh|v>)&8&+lSOKEl+!i=yx}Ug-U(T`BWb<Ww$P)LbrPrb9)=?1;f*BDZ&nveKdGA|%^4V9c){o!9{O}P(VFj$XSYY=xgloj z8VY_NHLr=>aG~JOrp7kkUw+LOdly?ath25Gd==!HL?6Kcwj`1QRyLc?|vO_4X#qCQaH%=IP`8GAmL?ZkPDn5h`ng8umExi4l zmdZho+>q1~7-KRS{!UjYh9uXG{Ui%}#znjRsbpl#zpvIJKiKg`dPAtW=>uGZhtaZC z?kfCnNG@ix`AdKQytU}(F?!tuUXV%TG&{SI=00000NkvXXu0mjf88!T# diff --git "a/Neuron/Assets.xcassets/Icons/\345\217\221\347\216\260.imageset/\345\217\221\347\216\260@3x.png" "b/Neuron/Assets.xcassets/Icons/\345\217\221\347\216\260.imageset/\345\217\221\347\216\260@3x.png" deleted file mode 100644 index fbb18a92e92dda134ec246ac19406341563f2271..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1540 zcmV+f2K)JmP)Px)zDYzuR9FeUSZi!lMHD`B@7)C!+iojGCDrh#AZ)irAT>NRK#J|w9|Va}62uqE zFB7Ukf{<8^8W4gIF!4nqL;~Uu`BBpr2sV*tq$sotf)I$o0)_7O0ex+Ej^Av#?Dlr= z?)HK6XXZTSoA1udIdgUe^`niQb9s20(_KKiRw#%HNYHp<;b!7t3PzCVWI>HYd`U1L zW7ZDUcGUb%)pNU3T?V+xTdwa~_HY{cDx6;tg7TBk=<#48Nz651x}&vC?z&Q)*_3Q< z5<+E~*SF$LA;fBY|F~peiR>VEPD=jRUzxEdk)JK0N%L=xaT>$6VMi7w0_d-enRiG@ z@A|7U0`ca?CFCs$c!`ZYATuT2q`~Sy>UfCd;=d|0uUN7A5Smid=rJ^-oP-!_bsl25 zF5K1qy#HX@a zytA7!MY^aNY-pRHs%NIF2=y(ZRc^06><%hjp08Ac`dtla;>Tw;0 zQgWJDg6$h;It-=$Td4A~x92w7pqi*MDr^jyK9p&;ch1dHlvX0pKDZR%R zn1wZx1dMR$20df2&6K=AK!6?~1rHVGe&{8a+k9Cv{z0hebQFYwy)n zxG$MDns(9w?6vslm;`*!+Gha2Yy%UVeJlwSEcj>KnZu@wu4y1AiUEf3)o!6%X4*X2 zk1tZ$*p!p@f?X*wj{$0zVfk5%3>FkgdJ!GVd*dRp-;h^*8b?j{?EmJYmMDK+^1 zU=8h1xNDU|bKGDdI;fR4nLt?&xt0R9f9Zt9Z3Cnl@R>O#kbTchD6BrW+V5uLm9hF< zUt>Css+TU-Za?PhD#|X{J47ugj=x2f*Czu^jKW(KT%bLc zQ;k%-b1-Cju*Ja(herL2(<;NS6OOlF#1FO&W<6t)LNc72?`y6Qy1oyu^bAu!&|W=7 zLdvhzd_)=1osPi1v=Lb+0X|*H(Z+uU8rpW4a$ZT(bXC*fj;M#S-w(2hW9{)s?>|$y z`B$aK4iTwJ4H;M^v9OZOhP-pSzGh5YUg53D`6Vsi3D$lCnHOOnGBQXzZ4~A;>ZH`V zz>hX>N%JOzYiU#iXBNjv@Km_Eqxzv6v6QP(HLNocBRgaY!C=c8l%V@f86;ewJnz1d zA>Liv@yEvKdX`uC>aj=L+o^2{Du9FqS1iCl#|ZOZx|+L|TmKp9wL=R0V#Omj+vv_h zHDREOC7Xv+*ab3+&mYNdwfgoUq#P8fc5YaNkpC1HyVE*ppfWzP6*beU+KMlh#v`L} zdtK^y3weqIvo%fs5SP6$o;?+v$}{+(RxTuebf(IED$$&zgnA~c|A|VAP=UP;;CaYb z4<-UNZR!m~SRX(nZLh1$tTy$D`Vc92M_bNzm>`W0000Px&1W80eR7ee_l}l(-K@^6)v6YC%3c*E`B1o}{R9Cu68w$0HfG94ybnBya(+3p< z#f2iV)VC<^q^qoqix#43Nm@`*w1T4b0fj;d1f-^=QS{H*=+VP zV$*^W$Toh#WxVoaQTmXf(PVpi_f-B;+_fM9*u?7=ep}tfK~|rih%1#bP^U zp#f8+<sp=#sQ{hT@^1``t#T@1JACD&Z`bmLGd`o3!F+Rhw{ zdI3>VWv?~vG`do$)FK3VTdFlFsMO&aX*o;pBVG3}J_FC`b}2ukGM!8&7hD}39lt?7 zIeEuH&MYW7zex{C;i+eV%*iY1!uRxRL+?XXc4dhC+}cS z1y=;Z_Cb0_FMl}`*u3Nb-R!Lwg3+V~H`r@pug$>aIiDl){eeJW#=ZrJTp;{Qrr{dq zS4iP`;oG+;pU)4X{9+AG6%`+EGMS9J&H~sG$bxU8fp21jX@jg+(WW_QkkowCTsr~* zc*nnr0n_K&;6KJ!<<5m$A|L?jU-)Ngr$`qGH!0Y14IBAlFQ?P#HuL0~Q<5SL>X9dR z2Pr$kEs@7?4`Ut9gd_jGQ}kyXPx*P)S5VRA>d|np|Bby)p>b&D?_2s@_0O6Oetv$`5&{hv*#uz zCVs82uRouyaWf_W=nVjlazQ$y(|?A3{n*$)Ha2z$&xof2rN9LG`ua9A!b{{k1C8XZ ze2OKux3aSGLvPh98omk0`p(bK-^*+@(%pBzD?|CK;guHl$%}P$bwhr7_yW@1-F-Xj z{2dH#@MAO!IzQr#r}4%KFI%1jdU|?l*`O^LTJB{mo9%ewBKb*3eBx%;m4JX}$s8hI z?`C8U+s5(4ZZ{H+1bTaW*8}`0;O2GaHIs3P<2F)NRrRH&EhkqeY;SMB0eLfdp}`Bh zCG5XuR>Mv5U{6{C#l^+1G5hQ7$_w~>3wT~<{FD`wB1o+Jb~@xQAYkp#Z0ZLpDk?s< zHYRyMA}qjPS&ZNXG`L=FYiqm4kw9r_>1LGfvMRXMnUU|wAE7hhrsvQ$h5S?cpLA%~ ziPx2slx(*)SUnKpyno)>=vHUof$8b#FfM(N`VMq2yXjlo(Epp`@u~9i@@-;+XU~A8 z`lI_Cw*qr{n*@lqYfzGlncp;AU0r=qlp*mMfF7pmNdf?qi75M?^u@*`#cxvgnKp7N z-Jx_%0?d8{VUcR^QI>PDdtB)eY(D3_-o^7JC9{_@^W;X(^!HU8@n$Q_cB^{!DrJ9K z^-ThFto7<_ZO2F32!;lb9q>xB}e+Ht)%u65o-isX}bFDlFNY%VU7}$+J z9UUD@=zLoM1BNPtUrl8nKpsl+gt?qtz{?#gD0n%I*06t;$(RO#qM{;s1-wxN zL6eXmCy~JT5$rq!@ECcTteg3?z$fsP6c!fVEOmnbZeKyYAkmmkA${MrdLlt$7vSYa znT=&*#g&WQRU$BsBX+gCjAxX@6BrOr$f@+M7?ab^#c3L` zcauoIpX@Ipv78>-u>Hv+)H(5g75)$xTPVlao#xmeMxe#>P&@BpBBwFD-vHXa(I_$@B*(o2?_E(brny%BCb~L zUL|Bt4i681r}RRqA|=P(7J^Vx__wxe>SvJsOumO4br^OrW7sMnF2w2YO9u(hPM8FLwU^-|AFAXUKL^wG)xrgE#sV{~E_$c`W9hQ_O zq?FjuYuV7UC*);kA=3puy=)4H!(Zz_Xgwf`1gIbAC_&~=UB`KnNq~FA3A|u%Bp{7+ z?1jYrR0rjGJ`+nut_nL7ucnSeyAqIILV}{6taYL2%t1QCjWyy0UdHt#AW9e-;Hpta z^$oIw^0`V(eEa}Exz;jqz9!6Z6FVW~hv{nK#2x}s*hwcHIl#Z--Tc+WcO~B@e0jiX zjhX4>E^;>mN(7dVSV1m;R#S4Fkhi#FPxDo z+;mg3O#j%3$&6Vv%QD%9y3It83;{)Au^W_f+00nfO`}XiLCKWalrkx`^t#XY?3|Nx zPS1JYbA4?)pXA}W{w~k={XM_ucFLGBABCI<6yxye?YD#;0dd5m*X=!PH zTU%RiLqkJ!NI``NhcK*6;+TQXKLC<+DYAM z>fRx~PJCrxVBiSA9V;YonFF+Q=g#x0s;cHQ;A@FB40;ynWMbq1NT;DcM!KK4tD~di zFDq88I1`ys1cJ-}I&k2?rRdSXfL4)S7D1;dU03Hx&lfLV{9Hm_F7ng>0+{Ila|fW@goH|$lmZn1sT)yS zdV72CUAlCstSfOy1E8XV2M>M|i+-jjr7B7j(tE@Vmm z8NjSbqC{4-a^wJY8~*9_xH4h`)Y{s5HB0p)fTQJl*akp4fsTy}7cSftN6AH$?r!CI0A&ROkf3mh;7Oxi2%|yM0t7n12FttK1L(3 zU3<9oO-)TXT}Dm|TE2YwY6kv|5jglzD)YnFR#a51&DA;32kqXy`}_$LCj1rRPvv4T z3ffL_cDriHk|js|#u8{jnBSfM2jB>k$z0(5*bf}l`C1Ss^I15tztKK1h;TI3v#OnA zJT;3JEz*o}1z!tdZr)0-#NZ&fjrc0OdR*O@KTm|E4cL1 zLGmmNjZ_z2Mk^0uVE+LZ1O(CQhj%2Wn~?DUmQ_S zXZQ0MyH@NUIxAfkv}MZ{ov~IqJH*U)Vm+svwCq}61hjhUjyvn4CGxY_}ZL4N4a zA-#aKN8uIFw|>Wt9j@C@7eLddO}iXHR|XJAslH(R`0>|!2^iQ5OgDjKS(dj;VA%xc zMlU!bz*4J8J|9RNzTuK7Q>H9*c6R|p!?i@~ZOYj%Y94DXevmnRotIFS#J=Mz4EUJ0 zEvQW2)3iCKmEDzd;#_{zN2w^YD-&d}@;WEE3m^n7aW*E*PXwe*eju?a`#ZdEARf;r z!?c{$EWyuu`|jPl_e<0z_G?bQU{Jkb!v@<(Y=9WZRFp~k`ZN$E2K*1?<+LPO4hnzW zZ(0eQapT5qrG2_{fJyW4yLir=IkvO60YVwQcuq|!z=u93Nc6t}*+Zrqb%sOsyGz!z zV)wfp+C(d&kFKh!WSV`}YO?`iI?M(=hLQf}H)*&TD7B+0Q2lBpkOudJ8=|N!R%K99a%+0w>e=H~4=BIYzJn z!h%wr%a#C7IuYR;{VYk2Tl*y1>;1q1*e}8>b8t+3KWXW014KnZUMT=T@AbZxq}F0k<5-f#4{7pz#o}!P#JaD~)ZVii(n@?fz@Srk&a)dg zlfJhADd_z^*Tsh|J~lu+Gq>|Aruwb1z>(zyIOH&AE930Og}>{TPs*Ej+$+hZy#;uK zDQ@$$0g_M#)S@(vplJo48~uEaxkG*u@Mh$bMV~4*jF0;{?l{-C`Q&k6-e;{~>gxiC z^0N%gSpV}e%F{kT`z?N!<8a7%!Ewk4JiEy!G2OmJ-!4c0xLNH7T^m;yK(7+u{Cr&Q z4!U>oX-$r@k>$(Sd1SO&IUTaMTTs~m$#1&B_sXFh(- zUcp2@1Rz!NYu}GR-^YBlM+kaDZ>RsK;g`jI_Z#r8ASV4WHKa`}D0+H!1=kd_T0n9= z`83XElL2N{M&L+YePfsgI3M8X9}q)t@C67nJ7$99?LR8ZSoub68Tz0noyjs8e|8Y_mEeZk9s zqwD4CISRGr6Q0+C#DL55mq@-1W7jS-AM@eZ&KPu+x;6|r(mxbH>S&(IZ$?13$|E%B z8R>Swc`U5{p#c&@7;i?vNVTM1e9rfo4ZjQvj{1f2L0*Qsn_+jXzgNA$+cr$g{{fuu z0*?MtGE#^IkR)*Ja3zjg2hh2ZBo@B&QLO&vus+ADUZg4JB-YlPlX@+}AEG|+`PfNQ z=sdbT!5M35TyUgsYzuM@mMQmDEa|7jOP%!}RvutZ5btSeY55(0^Ooo&kP1Ko0+6Nx z&~0QJNz=Hzii7VNh#n>0&#L=&90p#R)Bv&=>(3l{*Ir9`4L}vdiDNB#Ab0{t{2){R zWlNVd($oR6l){Nm=XzAHpsN5%jy&z}k2G~&g8x=dMzHOiAfZ3qA@&aQ68{H1+w+jaf%j wEwU7@O-OVRa!pg_RGz^i%5a(6Gn9dWWrwY*bzaydp!*p-UHx3vIVCg!0M=4eC;$Ke diff --git "a/Neuron/Assets.xcassets/Icons/\346\267\273\345\212\240.imageset/\346\267\273\345\212\240@2x.png" "b/Neuron/Assets.xcassets/Icons/\346\267\273\345\212\240.imageset/\346\267\273\345\212\240@2x.png" deleted file mode 100644 index 708a2f8b8300508a0af08563328e0cb6dcca15e1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 401 zcmV;C0dD?@P)Px$OG!jQR9Fe^S-}ayKoHESAXtH*`0`@~HefS8t;DBY`15B2R-g}GqF@Vx8fQZe zaqR;Ui30*RAXy;41@0km zDJ7??)`&4krUB6@7}pwhYIRkcUR5aLFs3XHX$g$aA~0@V80G&oq9vANZrkJ zO>u#knSTbwOY;V^iJr)^PaqCE*M&3DK)z9cH!h72#-78%A?OI4pS{6_Vsx6B~^-Imp_Q0GfjdX0fH_AZtqkXbvWr#g_h!gI(tGIgBZb vLt5XcubAeH^qVf)^bjTvt*`uz{SEN}xEn|eVzs-A00000NkvXXu0mjfZ2YTd diff --git "a/Neuron/Assets.xcassets/Icons/\346\267\273\345\212\240.imageset/\346\267\273\345\212\240@3x.png" "b/Neuron/Assets.xcassets/Icons/\346\267\273\345\212\240.imageset/\346\267\273\345\212\240@3x.png" deleted file mode 100644 index d3c3a7069c9085739d3b108ea0c67e4f1d8babec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 550 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw1|+Ti+$;i8jKx9jP7LeL$-D%zA9%VrhGek5 zjo$6YY$$Msk(EV?t4d`_4S)Sad50R7MGKzl|4?MHb(P}qu<4U*)=tS5y}K^ia9Y^( z-FI)_oV#;+PO91O^yH2gGAH*E`#$AzbQD@3?IQZcG>xm|N8ulLcYToDS5^gQ1 za6lq)y{OJFiN|(}q{{M-a4l$C>+@Agy|p^@T-0`vZAMqK=ZL%uwamU7bmLUW`of?c z;ZN6;dM9cJ?>*-EFmCGRYr3rv4RfdbHh8l2h{UBU&fi`f6VZI$+y8U&Z(;stHmz6J zsr@<^a9nSjT=CO0J0I;S_u^ZSEfaCbM#}Bhl{YfCgN%>F9^RKA*SuqU#t$XU9@%vq zGL;IF#hVs5C%;*in0Zw>J}vH4Ueui9l`D_m*}#4v>{-a+XwB0tuBolw{^vOt&RXte z`)s+%_4N6Pas2v1w|{LtB3FN}+fiPw{DAh}s<;Q%?Y4(()|ox?k(cv)cz%va%ZjmzTz=s^!17F6T<~m&TseQJuVb%eg8>!){PJ!mkCmR2|HEWMDkA7v1}6| zo?VtS|MC*{sdv6q`kepQ$TL%uPx&OG!jQR5%fpm0d`aVHC&rWr88?A}R!>8>702z*RU%okSPWMG?&?k&4)4LRUd1 z5q4vAx5()neNa?}5CjU28Ope7!VKmS*%E8iIkg;uqSM9kTLl?~b(DdD z0d+Z6R#vu;tN4{$XgOI|H#IeNmpFe(Ny+ADG&*D4aWEKM#5e?s{M08CX}qXPGq6sMVM~eI~sH$44tFhwnsg0;Q&H27V2Un!3U0 z_A}714f2G;;cD(yD|H%2$h2E5fN%`xG)bV&Yr#oy8H@mf$nB=NDD}P~KXUtgz6m_5 zQCeA-TV|YC5jYEOf+t`e%mGbZas%iG2BlJ|7gbeN+NKV0%0;&>x7!`cE2`iMpkLcF zDS0%E?geXe?W|>t2KQ34*ccv1&=K=VzoraG=R44~MWQ9YxU4z574v+dpSy`zMw9D` zRJ72hRAbj#eF8R7qk&p+x=yR(N>;5EezCc^`M4dGz0Y3s6TlC&K`THVXat&veHwCu z=r<^OpW?Ol74S1{#dv_b-bR2$^6vqMfSQo5!D&k#1DnWyMSd^&7i3fy2GR8-cfH2a zO|CnB2(%R%rt}X$#q+6x8e^7GT^P?E?YAHfT8=!fHPg%W4iqY?cRIxr+hpY#g_Fr- zDTUYLTv_OCwSxb~&74{C>$no_fA)2gfMFwS2#gTm5jRK91CCI>g8bI7Zz!V7wk?(-(6gmPo2sz9wI*GZ5L2vRV_#Z_TXm5oS zk&ePg7P6Yd!ET->c|RWto3;n=HcpX{&THnGo}Mo4?(Y6(`VF?EaIRA_f2MwhGE8yf nbbMfrC!n&ct7|epHdp@*N=#M!oAaWN00000NkvXXu0mjfO8A}D diff --git "a/Neuron/Assets.xcassets/Icons/\350\256\276\347\275\256.imageset/\350\256\276\347\275\256@2x.png" "b/Neuron/Assets.xcassets/Icons/\350\256\276\347\275\256.imageset/\350\256\276\347\275\256@2x.png" deleted file mode 100644 index 9183afcb2452dd4599a84a84aef5a37ff97c7af3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2162 zcmV-&2#xoNP)Px-EJ;K`R9FeUnR#rKMHt4rZE0(f7EqA_Dq@V_1zIRztO2b;Bt(sh2Pzts9)O4j zjQWQpN=zz(+LqD+B_L7oh8P2QP|;dM@K!WlA(TR*NLy%up8lTg%=+!__w8-@$_iugcEO zuHUg^$MI1Ayu7>t?d|Pr(Vqh0?PX%*TJA-p`bI-^24!Zu*vrY@0cB5NQc?2Z1 zwY9xcP*5<+=_8`_v%0>%zK-$*r_HVIb5lwuvUE^FHQ(XW!7P4(Z4bJk)W=azHuja} z<>e1Ld;?V;dOB}mol&$9x0$%FX`cs8sCV;?AfyiwY0Q6LEI!r4aT{i`flnKET?U}h4*P^nodfY z=~>u1oSlXdhU`lO%`r}XvZbY^hn+$HSDii557Jv1f3(JcY&p3*&q+Fn@SHhw(oUQ> zQP=6~Yv8Ge_7qt=2^>P{)T@C~p*HeFNLQ*Z3Ca>hMMVuf%^Dea$}})R^_~_u3^Zfb zupGa0jvqu=hV=Aw?a*4)A$*_8o)pLyFm6A_>|l0hl$4Yla*iKHxUjHLyY)jk5$In_ z&Vc{B&Y0uO7&}=CABB$RdNlKtdkK`U)>BtRGcz2h)bac`_z@fhvC4UJgoEP3#Fa2! zBaNqjX@o_EE6zuCJGdy^y=yZ*1$0>L1WSOcFmY}dFZtGJNm#Obs4qxL zN*WOZ*>mv~S;=}3jja5B0$Wuj!rINi;WpCQ{njxaf_7ac zLHcTpn;~8tWyXJ|gTG8CcT=X@4kMKM-gbkr>w&fYJp~T8V3gldU{BE|5grR>fafsO z11*nPoO(*$UZS=5nlbjFly918yA@{`ADy5Q8{63m`DA8qg~RKp)8W+)bQh`c^K|+` zaxjh?K_>bpzORjp@_lVKepi}4j!?b~J_jd^t~cdX;C5iYU2mlRDsYv#bLXCU{P^)7 z;Gq4X3;dmY>-oew2R5A)tlj-M-o=w>z|_{()&lG3QI?^vGB(Y${42mqpct$GGk_)g z9(CQ?JO#2(iYy#jNoO}Z$toy_uoRpy+6+5YCuqFN2%=-T2rL73O)Fgu0H*`nzwQl% zU@g#c@)XGGV9~PN!UXzxIGqDo!l|VG2goKUmd&Kl?ch^z0niE74~WbXIrKb z8ymZNS1@CBzer0<`vB614Tsy9MCI^+1Q90ZZpT@vNHU!IX1CYUY5wm&_0wxzU7dc} z)TWhG@Z-*(oz>1ydl3ofaMo|at0?c264KRN>L>Gha%kQQE(iUw@5i>aSFc`+ii?Y5 zxxmM48!SQd3`inI zr)U*rC1|ET2%G|R*hD1jx9h`8G}E=UwH4TPIoiN$o2efRtv|Ci8nSQDJ_j60PENiM zdyTcL#OE4(K2V)LzTs|g2YV%ut9h7-nAUEw^xPnyp z38ZxMo6i?mz4m}WXbX+&ur)yTV#w|b7)@o~ym|fk4`meh27Td-cp}C`ICOx}HnXVz zJ#^UYVZfEvj*g~p5oI#vVjgC`wRN|2J(|1G**C<9@Ce8~?~JEyoANnn3ChFW?Tt;i oiCE8X&&=*Vh=17K{+)6E02AR4O>OG2Pyhe`07*qoM6N<$f}A5Q=>Px# diff --git "a/Neuron/Assets.xcassets/Icons/\350\256\276\347\275\256.imageset/\350\256\276\347\275\256@3x.png" "b/Neuron/Assets.xcassets/Icons/\350\256\276\347\275\256.imageset/\350\256\276\347\275\256@3x.png" deleted file mode 100644 index 97a8b480fd03b3b1bfd9148796edffd1f3797da4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3391 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 diff --git "a/Neuron/Assets.xcassets/Icons/\350\265\204\344\272\247\350\256\276\345\244\207.imageset/Contents.json" "b/Neuron/Assets.xcassets/Icons/\350\265\204\344\272\247\350\256\276\345\244\207.imageset/Contents.json" deleted file mode 100644 index 324dd309..00000000 --- "a/Neuron/Assets.xcassets/Icons/\350\265\204\344\272\247\350\256\276\345\244\207.imageset/Contents.json" +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "资产设备.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "资产设备@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "资产设备@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git "a/Neuron/Assets.xcassets/Icons/\350\265\204\344\272\247\350\256\276\345\244\207.imageset/\350\265\204\344\272\247\350\256\276\345\244\207.png" "b/Neuron/Assets.xcassets/Icons/\350\265\204\344\272\247\350\256\276\345\244\207.imageset/\350\265\204\344\272\247\350\256\276\345\244\207.png" deleted file mode 100644 index 4d373298c42af1e0ccb1eb2fd80ee43692a24166..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 683 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 diff --git "a/Neuron/Assets.xcassets/Icons/\350\277\233\350\241\214\344\270\255.imageset/Contents.json" "b/Neuron/Assets.xcassets/Icons/\350\277\233\350\241\214\344\270\255.imageset/Contents.json" deleted file mode 100644 index 6cb5fd17..00000000 --- "a/Neuron/Assets.xcassets/Icons/\350\277\233\350\241\214\344\270\255.imageset/Contents.json" +++ /dev/null @@ -1,23 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "进行中.png", - "scale" : "1x" - }, - { - "idiom" : "universal", - "filename" : "进行中@2x.png", - "scale" : "2x" - }, - { - "idiom" : "universal", - "filename" : "进行中@3x.png", - "scale" : "3x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git "a/Neuron/Assets.xcassets/Icons/\350\277\233\350\241\214\344\270\255.imageset/\350\277\233\350\241\214\344\270\255.png" "b/Neuron/Assets.xcassets/Icons/\350\277\233\350\241\214\344\270\255.imageset/\350\277\233\350\241\214\344\270\255.png" deleted file mode 100644 index 4c1db723024cc10540f13d8ce39fc5ddc459530f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 600 zcmV-e0;m0nP)Px%5=lfsR5%f>RlQEaP!y&=lF-S;m>|Z{I5_w>wxJ9>fkqM^02eUo6ZilQ>VhwT z8ubN;l|TmvgA69RN+fkM8V0&h>UU~ON@;B&-sGhB-1B|kJ@@w9dzsiDty-;4a~!wE zvaAHyt_>}jrm3Re5JmATh|c&0AeiELzQ8cdHsT9@7>uKAv7Y29vjBb&`?)>b4EiM=3EB4HIT5N)~y zg1SjtE5=Ysp#q-my$T}i;O~C`cOL|B#<8+Cu**n*$Y#lc1#84F(zTfXZVNcp1(b4p6h%F&N7TT!X zU`-Y`(>KuNP)2k>nKZ4Fr|bF&cv^IBAX2lnVwNzSZEQ&qOE3Z{Apzo&25Ax05~dDr zrCS8|ClMzOaY=);sEri;1Q-1~bPl~2{fIElrYOo>JRYZ~DF^p|o+c7@n)8@q0ug3C mrdYD|nBp`x?22;WL*F0#^6OK~nTTir0000Px(N=ZaPR9FesnN3U+K@@;(X$2z&IbeWz;77$oiFzQOXl;TLJqSd@MK}>dqGvtm zfoLKJIctJHCpZxkz?({d_M~tUFcAgH31W!UL=!82^?Mt3v)yjXcH6?T$uNKKz4>P5 z?J#}oI*{QrFfeeayu7?>YisK?rY^bN?#r=QtU}DXT&_jhKTsUs+}xa9Sy`EHZEgK4 zeRdz09mTP+u>!Bx8vv{sC?9zxdHxQz*60r-8)6beq|rbi5Zys1p*t<$;o;#U#l^*K zz;%+&C0I+N0q`lrdskOi`*rDn%VBh7_PNy+@jnx@Ll!w^@CRAcLZkp zk9s_wr>y90D+cY#0R3`fW8*$<@+*~%l)%AY@Fc6+2kgaEMzYlh^aQrrS?zDhY$gQ; zwl*&>PqwK#nQ|`LfnFfB_s}eMJp+TT=k4D^M&Ny5Gu=p%T z)_Vl&jKaHIpIl#Gzhd-hw-Ex^9-FW%Ol(6E`ddc3HtKcTEODxFz#Lso^mfEaSgZg# z$~In*M0voX+D>8}yi3>hLK-lxP>9byp)qe5_VQn$feWe;*{zZHdRRj z=0s>`pfhTRja;+E3TtH>cH&fo3AQ%1Q86qR7-n)uZFE{LY^-vDa4Ep0rKLW~XPpMB z=Run%ArMBX*O05|LxVBWO`H-9Sj3ox9OS^JNqpESFcr>|$T?7{dV2Ia-e+QY8> zz*v|ROT(xamgC5$y32$;7#~3}tH8_4%iTpqMLlb4YnFiv3k$3G(eM$L!^tK|--rN@ zQMj8-D2;aQ4ot8_Vi1xnKU{5pS`Z%hNJNI?LX2m07BlHu?yk2uD=n|80q_`$=P|VD ztHLJjS=k~3p3?W)DAxenZ$nR0fk7*q6v9hOOABs^bs-os5pYx!s~-|pglZFxpEUkox z?vYJu5_?yel<=!@c+-=bpVW>G1}3bq%m|;E!=IZe&KqaA3-x&!TNup7G6fbvd}kVm zzltAyGE>Y&8)Hq7uF2TR>?Z{l1r)zWqtQl;yhvssTkSH2#Dr5t-(@UQA2x%K$R>A= zN2H9t9vN)|Iz{Rt;yz^TGpkz$7G;SdawBWwI@(F~yqQ6(Z9sk|^+qC*{+xXF(t}7~ z4sbuK-9qv+a3y)ZUdC8P<{vU)@(_K4Z2vefR&GBm8X=^v|8y&H1AruKP_-?)0OSa7 ieUQGda-Yrg)9rtrJ;ktPx*`AI}URCod9oLg)ZRT#(J?e=D^Lc#R%f~Wy$Kujb7>-MtRDpZmtJ{g0)h?FQ6 z9wZVYrcz&!2NW@}yeVE15}(9U>}7jdP(w^4XwilieQBd$QA=r?Ue@0?>`u1R+0M-F zY`eR2lGAhMobR0P`~Uac=S*F9ERt()Z!gTu%zVJ*a;?F8@f(A|;3~ppVgm7hi2p%& z5g)+!&(6*cc6WFG)!5iLn`kp#iBizg(sDO$Gk~fAxP@>X;R^J*P3oMb%m}jm_yE3b zVq)S#O-;?eCd*qAvz>_E-rmhv@I0Vv2_Lj%je#)43?gzG|3!Iu`40x-ma^?4!bKC_ z=61XHKtMGXWLmPqqA-L^Q=|ibyrZL|#g48MHxWHOJ&yzaE&NuCcx(}el(YDzii(PJ z@l+Ty5q`gaC4=9a81V`uWXEGfyk)Urk~*3R--VECvD6n65nWwfK99%q0SY(7qBMbJ zex9D5Zm6oN@>|tmb_7@Jr7OG!3Ez;AY$S+{>%}$(?7gbNCL^X>1YNZMe*Ou@f)~wHnnW_6O-)U`?DP31jBGOO8qyL{ zlmx-PgRXL-xw+YJIyWRjxOqMpH2 z3buoK_0J=MRpVn6*sUjQL)r{m>4D11%B$k3?+UtPdAyizw_wyNoI}K^Q>U0DGTuQb zODkyCGr&C&b$MtJy4^EE#?~nMvjwAIjjVVM5ru_?Pb2tbq(C~x!B|~VQu0(JHkF7_ zYmha%nieUTW^rkS`#8r?v8s`xqM}F1^rac#h2Z$ur3z-nsYI}5*g=ZnQZ!U99fC~8 z3c;*6g$OxlWzgClPIOSXU9byYg^0Yoye$kMs~tpu0#z`}NxMn}O+*e{o#ZW zW(yw5oXA25bD7K@?t+4Xl_acnFcA-Vz21A>99-N_Uny`f5zBLOa#k~Ix}`O!)2P!3 zp%KVZd##&@*J}qKd_vq(2CsjCb>>#QAnWbwa zq^$8`|9A7c(#H0Yub5rBx7EEcp)T*$DkGAarL0;XR2 zYIFwN9IO9EV#05&g%k(b>CR}3Skgxt!VC?e-^06wL5v%TpW%`O9>tYl<;M-Xl#08OJ$V&|zKnS-TxD!M6#gwMl< zt3>dI_(u}`;f!<-=@y+K%nH;wQy8riKf`R%8pfu!2uT1Xg<()-nsM%o#N1likEYCm(B(;`G6Z-~DmXqI@I zx{tLa2+ny#NFo=m4#8ZX#M8-v=@+my>0z4JHNrxCh*Cipk?VzbhSO3!yu}h>D=ofP z4~K4x5J4_`^{_=hpjRkG)82?ig5Zftgk*9#^FzpZU*f6k1Ox9kk1vE_K_a9etFQg^ ztxp%KB$@ND{iI+pveA$TuJ})nj*jkv)lZBnOSXLZ0T~x|2?itE42h5m;c>^6>7HtC|`+8`vfWO-u~A!uLetTvO0Xu`^5j&Q@>9`;fM&x~bm&-0N>l zNBA_4ub%jV>5b7hL64hLFM9b3O=w_IQD{8KRS>vcUEp>P6HC6>5kbMtJ&pUfZS2)r z_iNtep2pDD^kaIao(@bP)om&VQa4*3sGa+%B=C;zo%4+|Qh1j9KJ9huUvv3n!woIA eMrD6ZYZ>p=Dl&4OKkW{5ErX}4pUXO@geCx?`dmT) diff --git a/Neuron/Assets.xcassets/NavigationBar/dappCollection.imageset/dappCollection@3x.png b/Neuron/Assets.xcassets/NavigationBar/dappCollection.imageset/dappCollection@3x.png deleted file mode 100644 index baa1e6936652787fa25d06717134c0f95e0cffd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 361 zcmeAS@N?(olHy`uVBq!ia0vp^Hb895!3HExugYEyq!^2X+?^QKos)S9WGj2RIEG}f zzP+XE%@Qbb?Bny9*4=9YSy)eVu-!go<`}VIP2`L$gNQX^;)^e4T}b<+oMFV7w)+22c@@etdZl5;J&lVLz>}^ z?%%gUM_RA1*Zb$P=eJ&7!j8@x%hWhM9GHYAG_a^BFv6J=W-|P~ck$oDuwO5o{B>_n zP5Srm+oz)!D~i(7(k*pNPX0dRyxj80KXX5CXDij)pJmuS-m98@@sU<2hDks}T|^UN zufKf!`8UJ0R=X+=v9o?#|J~}(oL}QpEmG<_eRqw|@0cH+x86SOpE!T-R;|kmY~Iz~ p7Cw-apSi*FnypJ*z}Bx!^*RF9zZ|~o - + - + @@ -18,15 +18,15 @@ - - + + - + @@ -58,7 +58,7 @@ - + diff --git a/Neuron/Base.lproj/Main.storyboard b/Neuron/Base.lproj/Main.storyboard index 52d58de2..33abb69c 100644 --- a/Neuron/Base.lproj/Main.storyboard +++ b/Neuron/Base.lproj/Main.storyboard @@ -49,7 +49,7 @@ - + @@ -129,11 +129,11 @@ + + - - diff --git a/Neuron/Sections/Authentication/Authentication.storyboard b/Neuron/Sections/Authentication/Authentication.storyboard index 3934a7bb..ad9217da 100644 --- a/Neuron/Sections/Authentication/Authentication.storyboard +++ b/Neuron/Sections/Authentication/Authentication.storyboard @@ -1,11 +1,11 @@ - + - + @@ -152,7 +152,7 @@ - + @@ -309,7 +309,7 @@ - + diff --git a/Neuron/Sections/Dapp/WebView/SearchAppController.swift b/Neuron/Sections/Dapp/WebView/SearchAppController.swift index 7255b46f..f50cd7db 100644 --- a/Neuron/Sections/Dapp/WebView/SearchAppController.swift +++ b/Neuron/Sections/Dapp/WebView/SearchAppController.swift @@ -32,7 +32,7 @@ class SearchAppController: UITableViewController, ErrorOverlayPresentable { let qrButton = UIButton(type: .custom) qrButton.frame = CGRect(x: 0, y: 0, width: 40, height: 30) - qrButton.setImage(UIImage(named: "qrCode"), for: .normal) + qrButton.setImage(UIImage(named: "qr_code"), for: .normal) qrButton.addTarget(self, action: #selector(didClickQRButton), for: .touchUpInside) textField.rightViewMode = .always textField.rightView = qrButton diff --git a/Neuron/Sections/Settings/Settings.storyboard b/Neuron/Sections/Settings/Settings.storyboard index 2364d0a5..f2a4fb39 100644 --- a/Neuron/Sections/Settings/Settings.storyboard +++ b/Neuron/Sections/Settings/Settings.storyboard @@ -233,7 +233,7 @@ - + @@ -293,7 +293,7 @@ - + @@ -594,7 +594,7 @@ - + @@ -629,7 +629,7 @@ - + @@ -644,14 +644,14 @@ - - + + - - + + - + + diff --git a/Neuron/Sections/Transaction/TradeDetailsController.xib b/Neuron/Sections/Transaction/TradeDetailsController.xib index 8c4b6ed6..b0b43851 100644 --- a/Neuron/Sections/Transaction/TradeDetailsController.xib +++ b/Neuron/Sections/Transaction/TradeDetailsController.xib @@ -1,11 +1,11 @@ - + - + @@ -98,7 +98,7 @@ - + @@ -146,6 +146,6 @@ - + diff --git a/Neuron/Sections/Transaction/Views/TradeTableViewCell.swift b/Neuron/Sections/Transaction/Views/TradeTableViewCell.swift index db648333..30e686e3 100644 --- a/Neuron/Sections/Transaction/Views/TradeTableViewCell.swift +++ b/Neuron/Sections/Transaction/Views/TradeTableViewCell.swift @@ -24,14 +24,14 @@ class TradeTableViewCell: UITableViewCell { didSet { if ethOrNervos == "ETH" { if selectIndex?.row == 1 || selectIndex?.row == 2 || selectIndex?.row == 5 { - subButton.setImage(UIImage.init(named: "复制"), for: .normal) + subButton.setImage(UIImage(named: "copy"), for: .normal) subButton.setTitleColor(ColorFromString(hex: "#2e4af2"), for: .normal) } else { subButton.isEnabled = false } } else if ethOrNervos == "Nervos" { if selectIndex?.row == 1 || selectIndex?.row == 2 || selectIndex?.row == 4 { - subButton.setImage(UIImage.init(named: "复制"), for: .normal) + subButton.setImage(UIImage(named: "copy"), for: .normal) subButton.setTitleColor(ColorFromString(hex: "#2e4af2"), for: .normal) } else { subButton.isEnabled = false diff --git a/Neuron/Sections/Transaction/Views/TradeTableViewCell.xib b/Neuron/Sections/Transaction/Views/TradeTableViewCell.xib index 837d2c9d..d69bd206 100644 --- a/Neuron/Sections/Transaction/Views/TradeTableViewCell.xib +++ b/Neuron/Sections/Transaction/Views/TradeTableViewCell.xib @@ -1,11 +1,11 @@ - + - + @@ -19,7 +19,7 @@ - + @@ -70,6 +70,6 @@ - + diff --git a/Neuron/Sections/Wallet/AddWallet/AddWallet.storyboard b/Neuron/Sections/Wallet/AddWallet/AddWallet.storyboard index 4be96db5..3c9e0c35 100644 --- a/Neuron/Sections/Wallet/AddWallet/AddWallet.storyboard +++ b/Neuron/Sections/Wallet/AddWallet/AddWallet.storyboard @@ -53,7 +53,7 @@ - + @@ -288,7 +288,7 @@ - + @@ -483,7 +483,7 @@ - + @@ -694,7 +694,7 @@ - + @@ -801,19 +801,19 @@ - + - + - + @@ -1164,8 +1164,8 @@ - - + + diff --git a/Neuron/Sections/Wallet/Assets/Assets.storyboard b/Neuron/Sections/Wallet/Assets/Assets.storyboard index e9922057..98025e99 100644 --- a/Neuron/Sections/Wallet/Assets/Assets.storyboard +++ b/Neuron/Sections/Wallet/Assets/Assets.storyboard @@ -123,7 +123,7 @@ - + @@ -223,6 +223,6 @@ - + diff --git a/Neuron/Sections/Wallet/Home/Wallet.storyboard b/Neuron/Sections/Wallet/Home/Wallet.storyboard index e22b038e..ce653bdd 100644 --- a/Neuron/Sections/Wallet/Home/Wallet.storyboard +++ b/Neuron/Sections/Wallet/Home/Wallet.storyboard @@ -203,13 +203,13 @@ - + - + @@ -356,7 +356,7 @@ - + @@ -435,7 +435,7 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Neuron/Sections/Wallet/Home/RequestPaymentViewController.swift b/Neuron/Sections/Wallet/Home/RequestPaymentViewController.swift deleted file mode 100644 index a26e7911..00000000 --- a/Neuron/Sections/Wallet/Home/RequestPaymentViewController.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// RequestPaymentViewController.swift -// Neuron -// -// Created by Yate Fulham on 2018/08/30. -// Copyright © 2018 Cryptape. All rights reserved. -// - -import UIKit -import EFQRCode - -class RequestPaymentViewController: UIViewController { - @IBOutlet weak var icon: UIImageView! - @IBOutlet weak var name: UILabel! - @IBOutlet weak var address: UILabel! - @IBOutlet weak var QRCode: UIImageView! - var appModel = AppModel() - override func viewDidLoad() { - super.viewDidLoad() - title = "收款二维码" - icon.image = UIImage(data: appModel.currentWallet!.iconData!) - name.text = appModel.currentWallet!.name - address.text = appModel.currentWallet!.address - let walletAddress = self.appModel.currentWallet!.address - DispatchQueue.global().async { - let imagea = EFQRCode.generate(content: walletAddress) - DispatchQueue.main.async { - self.QRCode.image = UIImage(cgImage: imagea!) - } - } - } - - @IBAction func copyAddress(_ sender: UIButton) { - UIPasteboard.general.string = appModel.currentWallet?.address - Toast.showToast(text: "地址已经复制到粘贴板") - } -} From bc71c5ac4bea66502e264166fd6c2080f490287f Mon Sep 17 00:00:00 2001 From: cezres Date: Wed, 21 Nov 2018 22:29:58 +0800 Subject: [PATCH 144/315] Delete old wallet page --- Neuron.xcodeproj/project.pbxproj | 4 - .../Transaction/TransactionHistory.storyboard | 14 +- .../TransactionHistoryViewController.swift | 6 +- .../Wallet/Home/TokensViewController.swift | 209 -------- Neuron/Sections/Wallet/Home/Wallet.storyboard | 497 +----------------- 5 files changed, 16 insertions(+), 714 deletions(-) delete mode 100644 Neuron/Sections/Wallet/Home/TokensViewController.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index a501d47a..a4d1fd87 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 1A09224321357F3D00CAED5D /* TokensViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A09224221357F3D00CAED5D /* TokensViewController.swift */; }; 1A092245213580C900CAED5D /* NFTViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A092244213580C900CAED5D /* NFTViewController.swift */; }; 1A1E1B8B21A3CF3A00C24D10 /* SignMessagePageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A1E1B8A21A3CF3A00C24D10 /* SignMessagePageItem.swift */; }; 1A1E841821717EBD00B15E88 /* PasswordValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A1E841721717EBD00B15E88 /* PasswordValidatorTests.swift */; }; @@ -220,7 +219,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 1A09224221357F3D00CAED5D /* TokensViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokensViewController.swift; sourceTree = ""; }; 1A092244213580C900CAED5D /* NFTViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFTViewController.swift; sourceTree = ""; }; 1A1E1B8A21A3CF3A00C24D10 /* SignMessagePageItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignMessagePageItem.swift; sourceTree = ""; }; 1A1E841721717EBD00B15E88 /* PasswordValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordValidatorTests.swift; sourceTree = ""; }; @@ -932,7 +930,6 @@ 1A2BFA19213513D60065496B /* Wallet.storyboard */, 8928C27820B2A61900C3103E /* WalletViewController.swift */, 6E9B947E21A4086400D67357 /* WalletPresenter.swift */, - 1A09224221357F3D00CAED5D /* TokensViewController.swift */, 1A092244213580C900CAED5D /* NFTViewController.swift */, 89D84CEA2148C03B006B0287 /* NFTHeaderReusableView.swift */, 89D84CEC2148C056006B0287 /* NFTFooterReusableView.swift */, @@ -1403,7 +1400,6 @@ 89FE21D820EC7E1800A09302 /* ERC20TokenService.swift in Sources */, 6E3B2B6B219D938F0095257D /* SentTransaction.swift in Sources */, 1A5515E521898A1000D34791 /* WalletKeystoreManager.swift in Sources */, - 1A09224321357F3D00CAED5D /* TokensViewController.swift in Sources */, 6EABFB10219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift in Sources */, 6EF8F40C2175CAD9004B7587 /* UIControl+Extension.swift in Sources */, 898CA98320EA0F210059ECA3 /* TokenModel.swift in Sources */, diff --git a/Neuron/Sections/Transaction/TransactionHistory.storyboard b/Neuron/Sections/Transaction/TransactionHistory.storyboard index a99d26f1..653d690f 100644 --- a/Neuron/Sections/Transaction/TransactionHistory.storyboard +++ b/Neuron/Sections/Transaction/TransactionHistory.storyboard @@ -209,7 +209,7 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -422,124 +87,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -1135,7 +683,7 @@ - + @@ -1143,7 +691,7 @@ - + @@ -1151,7 +699,7 @@ - + @@ -1159,7 +707,7 @@ - + @@ -1167,7 +715,7 @@ - + @@ -1175,7 +723,7 @@ - + @@ -1308,44 +856,15 @@ - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - From 3dffc8713c918c63a56a87063bbd30bbe5d5ea07 Mon Sep 17 00:00:00 2001 From: cezres Date: Wed, 21 Nov 2018 22:40:39 +0800 Subject: [PATCH 145/315] Remove debug log --- Neuron/Sections/Wallet/Assets/ManageAssetViewController.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/Neuron/Sections/Wallet/Assets/ManageAssetViewController.swift b/Neuron/Sections/Wallet/Assets/ManageAssetViewController.swift index 72476921..fea8e7c3 100644 --- a/Neuron/Sections/Wallet/Assets/ManageAssetViewController.swift +++ b/Neuron/Sections/Wallet/Assets/ManageAssetViewController.swift @@ -17,13 +17,11 @@ class ManageAssetViewController: UITableViewController, AssetTableViewCellDelega override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) didGetDataForList() - print(CACurrentMediaTime()) } override func viewDidLoad() { super.viewDidLoad() title = "ERC20列表" - print(CACurrentMediaTime()) } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { From 6e73a2f5e5ba9a403eecbe79e5b2b49d40a58185 Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 22 Nov 2018 09:55:01 +0900 Subject: [PATCH 146/315] Move Token class to Models and reuse the global TokenType enum --- Neuron.xcodeproj/project.pbxproj | 16 ++++++++++++---- Neuron/Models/{ => Realm}/AppModel.swift | 0 Neuron/Models/{ => Realm}/TokenModel.swift | 0 Neuron/Models/{ => Realm}/WalletModel.swift | 0 Neuron/{Services/Common => Models}/Token.swift | 8 +------- 5 files changed, 13 insertions(+), 11 deletions(-) rename Neuron/Models/{ => Realm}/AppModel.swift (100%) rename Neuron/Models/{ => Realm}/TokenModel.swift (100%) rename Neuron/Models/{ => Realm}/WalletModel.swift (100%) rename Neuron/{Services/Common => Models}/Token.swift (95%) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 8855970c..86b94bcc 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -579,6 +579,16 @@ path = UIKit; sourceTree = ""; }; + 1A91E1E321A63561000EFD40 /* Realm */ = { + isa = PBXGroup; + children = ( + 8905660620D0AC120041D4B4 /* AppModel.swift */, + 8913437020C78F1000A17AEF /* WalletModel.swift */, + 898CA98220EA0F210059ECA3 /* TokenModel.swift */, + ); + path = Realm; + sourceTree = ""; + }; 1AB28771217964F500DCC14D /* Service */ = { isa = PBXGroup; children = ( @@ -685,10 +695,9 @@ 8913436F20C78D9600A17AEF /* Models */ = { isa = PBXGroup; children = ( + 1A91E1E321A63561000EFD40 /* Realm */, 89B296EA20F4CB6B008558A7 /* TransactionModel.swift */, - 8913437020C78F1000A17AEF /* WalletModel.swift */, - 8905660620D0AC120041D4B4 /* AppModel.swift */, - 898CA98220EA0F210059ECA3 /* TokenModel.swift */, + 6E9B948021A408B400D67357 /* Token.swift */, ); path = Models; sourceTree = ""; @@ -1030,7 +1039,6 @@ 6E7D3837219C173D0031177B /* TransactionDetails.swift */, 6E3B2B6A219D938F0095257D /* SentTransaction.swift */, 6E3B2B74219EDE200095257D /* TransactionStatusManager.swift */, - 6E9B948021A408B400D67357 /* Token.swift */, ); path = Common; sourceTree = ""; diff --git a/Neuron/Models/AppModel.swift b/Neuron/Models/Realm/AppModel.swift similarity index 100% rename from Neuron/Models/AppModel.swift rename to Neuron/Models/Realm/AppModel.swift diff --git a/Neuron/Models/TokenModel.swift b/Neuron/Models/Realm/TokenModel.swift similarity index 100% rename from Neuron/Models/TokenModel.swift rename to Neuron/Models/Realm/TokenModel.swift diff --git a/Neuron/Models/WalletModel.swift b/Neuron/Models/Realm/WalletModel.swift similarity index 100% rename from Neuron/Models/WalletModel.swift rename to Neuron/Models/Realm/WalletModel.swift diff --git a/Neuron/Services/Common/Token.swift b/Neuron/Models/Token.swift similarity index 95% rename from Neuron/Services/Common/Token.swift rename to Neuron/Models/Token.swift index 76116536..2ae26969 100644 --- a/Neuron/Services/Common/Token.swift +++ b/Neuron/Models/Token.swift @@ -80,13 +80,7 @@ class Token { } extension Token { - enum `Type`: String { - case ether - case erc20 - case appChain - case appChainErc20 - } - var type: Type { + var type: TokenType { if isNativeToken { if chainId == NativeChainId.ethMainnetChainId { return .ether From fe6b47451f399ca9d9530c388c8ba03c4e588368 Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 22 Nov 2018 09:56:32 +0900 Subject: [PATCH 147/315] Delete empty override func --- .../Sections/Wallet/TableViewCell/TokenTableViewCell.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift b/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift index 46a0c784..4f191856 100644 --- a/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift +++ b/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift @@ -35,10 +35,9 @@ class TokenTableViewCell: UITableViewCell { @IBOutlet weak var ctxView: UIView! private var overlayView: UIView? - override func setSelected(_ selected: Bool, animated: Bool) { - } - override func setHighlighted(_ highlighted: Bool, animated: Bool) { + super.setHighlighted(highlighted, animated: animated) + if highlighted { overlayView = UIView(frame: ctxView.bounds) overlayView?.backgroundColor = UIColor.black @@ -48,5 +47,4 @@ class TokenTableViewCell: UITableViewCell { overlayView?.removeFromSuperview() } } - } From 03e59c5edebcc05ec85144c9ecc07d54400d397f Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 22 Nov 2018 09:57:52 +0900 Subject: [PATCH 148/315] Delete template comment --- .../Wallet/Home/WalletQRCodeViewController.swift | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Neuron/Sections/Wallet/Home/WalletQRCodeViewController.swift b/Neuron/Sections/Wallet/Home/WalletQRCodeViewController.swift index bb7c6423..45d4754e 100644 --- a/Neuron/Sections/Wallet/Home/WalletQRCodeViewController.swift +++ b/Neuron/Sections/Wallet/Home/WalletQRCodeViewController.swift @@ -14,13 +14,16 @@ class WalletQRCodeViewController: UIViewController { @IBOutlet weak var nameLabel: UILabel! @IBOutlet weak var qrCodeView: UIImageView! @IBOutlet weak var addressLabel: UILabel! + override func viewDidLoad() { super.viewDidLoad() + let appModel = WalletRealmTool.getCurrentAppModel() iconView.image = UIImage(data: appModel.currentWallet!.iconData!) nameLabel.text = appModel.currentWallet!.name addressLabel.text = appModel.currentWallet!.address let walletAddress = appModel.currentWallet!.address + DispatchQueue.global().async { let imagea = EFQRCode.generate(content: walletAddress) DispatchQueue.main.async { @@ -33,14 +36,4 @@ class WalletQRCodeViewController: UIViewController { UIPasteboard.general.string = WalletRealmTool.getCurrentAppModel().currentWallet?.address Toast.showToast(text: "地址已经复制到粘贴板") } - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. - } - */ - } From 6f0f038a9f836a47fef00bc5f89197689f37b737 Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 22 Nov 2018 10:04:09 +0900 Subject: [PATCH 149/315] Extract creating param builder func --- .../SendTransactionViewController.swift | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index 060ebf21..2895194a 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -111,19 +111,12 @@ class SendTransactionViewController: UITableViewController, TransactonSender { override func viewDidLoad() { super.viewDidLoad() + if enableSwitchToken && token == nil { token = WalletRealmTool.getCurrentAppModel().nativeTokenList.first } - paramBuilder = TransactionParamBuilder(token: token) - observers.append(paramBuilder.observe(\.txFeeNatural, options: [.initial]) { (_, _) in - self.updateGasCost() - }) - paramBuilder.from = WalletRealmTool.getCurrentAppModel().currentWallet!.address - if recipientAddress != nil { - paramBuilder.to = recipientAddress - } - setupUI() + createParamBuilder() } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { @@ -308,6 +301,7 @@ extension SendTransactionViewController: QRCodeViewControllerDelegate { } // MARK: - Switch transaction token + extension SendTransactionViewController: TransactionSwitchTokenViewControllerDelegate { override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if indexPath.row == 0 { @@ -341,6 +335,10 @@ extension SendTransactionViewController: TransactionSwitchTokenViewControllerDel } observers.removeAll() + createParamBuilder() + } + + private func createParamBuilder() { paramBuilder = TransactionParamBuilder(token: token) observers.append(paramBuilder.observe(\.txFeeNatural, options: [.initial]) { (_, _) in self.updateGasCost() From 616f06fe8a3788724966cc37d1479794a04150ed Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 22 Nov 2018 10:14:56 +0900 Subject: [PATCH 150/315] Token gets type dircectly from TokenModel --- Neuron/Models/Realm/TokenModel.swift | 11 ++----- Neuron/Models/Token.swift | 46 ++++++++++++---------------- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/Neuron/Models/Realm/TokenModel.swift b/Neuron/Models/Realm/TokenModel.swift index 87280a45..3b9e4bbb 100644 --- a/Neuron/Models/Realm/TokenModel.swift +++ b/Neuron/Models/Realm/TokenModel.swift @@ -9,13 +9,6 @@ import Foundation import RealmSwift -enum TokenType: String { - case ether - case erc20 - case appChain - case appChainErc20 -} - class TokenModel: Object, Decodable { @objc dynamic var name = "" @objc dynamic var iconUrl: String? = "" @@ -25,12 +18,12 @@ class TokenModel: Object, Decodable { @objc dynamic var chainName: String? = "" @objc dynamic var chainId = "" @objc dynamic var chainHosts = "" // manifest.json chainSet.values.first - @objc dynamic var identifier = UUID().uuidString // primary key == UUID + @objc dynamic var identifier = UUID().uuidString // defaults false, eth and RPC "getMateData" is true. @objc dynamic var isNativeToken = false // TODO: AppChain ERC20 should not be marked as native token. - @objc dynamic var tokenBalance = 0.0 // TODO: Should persist balance, or store them globally. + @objc dynamic var tokenBalance = 0.0 var currencyAmount = "0" override class func primaryKey() -> String? { diff --git a/Neuron/Models/Token.swift b/Neuron/Models/Token.swift index 2ae26969..9c80000a 100644 --- a/Neuron/Models/Token.swift +++ b/Neuron/Models/Token.swift @@ -8,20 +8,27 @@ import UIKit import Web3swift -import PromiseKit import BigInt import EthereumAddress +enum TokenType: String { + case ether + case erc20 + case appChain + case appChainErc20 +} + class Token { - var name = "" + var name: String var iconUrl: String? = "" - var address = "" - var symbol = "" + var address: String + var symbol: String var chainName: String? = "" - var chainId = "" - var chainHosts = "" - var isNativeToken = false + var chainId: String + var chainHosts: String + var isNativeToken: Bool var walletAddress = "" + var type: TokenType private(set) var balance: Double? { didSet { @@ -46,6 +53,7 @@ class Token { chainName = token.chainName chainHosts = token.chainHosts isNativeToken = token.isNativeToken + type = token.type } // MARK: - balance @@ -79,26 +87,12 @@ class Token { } } -extension Token { - var type: TokenType { - if isNativeToken { - if chainId == NativeChainId.ethMainnetChainId { - return .ether - } else { - if address != "" { - return .appChainErc20 - } else { - return .appChain - } - } - } else { - return .erc20 - } - } -} - extension Token: Equatable { static func == (lhs: Token, rhs: Token) -> Bool { - return lhs.address == rhs.address && lhs.walletAddress == rhs.walletAddress && rhs.type == lhs.type + return + lhs.type == rhs.type && + lhs.address == rhs.address && + lhs.symbol == rhs.symbol && + lhs.walletAddress == rhs.walletAddress } } From 11b3cc1a032385bf58f2b90e9f9275082e08c929 Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 22 Nov 2018 10:54:38 +0800 Subject: [PATCH 151/315] Incremental updating --- .../Wallet/Home/WalletPresenter.swift | 128 +++++++++--------- 1 file changed, 63 insertions(+), 65 deletions(-) diff --git a/Neuron/Sections/Wallet/Home/WalletPresenter.swift b/Neuron/Sections/Wallet/Home/WalletPresenter.swift index 5621d0e5..757db88c 100644 --- a/Neuron/Sections/Wallet/Home/WalletPresenter.swift +++ b/Neuron/Sections/Wallet/Home/WalletPresenter.swift @@ -26,16 +26,19 @@ protocol WalletPresenterDelegate: NSObjectProtocol { class WalletPresenter { private (set) var currentWallet: WalletModel? { didSet { + guard oldValue?.address != currentWallet?.address else { return } delegate?.walletPresenter(presenter: self, didSwitchWallet: currentWallet!) } } private (set) var currency: LocalCurrency! { didSet { + guard oldValue?.name != currency?.name else { return } delegate?.walletPresenter(presenter: self, didRefreshCurrency: currency) } } private (set) var tokens = [Token]() { didSet { + self.tokenListTimestamp = Date().timeIntervalSince1970 delegate?.walletPresenter(presenter: self, didRefreshToken: tokens) } } @@ -74,36 +77,9 @@ class WalletPresenter { } @objc func refreshBalance() { - refreshing = true - delegate?.walletPresenterBeganRefresh(presenter: self) currency = LocalCurrencyService.shared.getLocalCurrencySelect() delegate?.walletPresenter(presenter: self, didRefreshCurrency: currency) - let timestamp = self.tokenListTimestamp - let tokens = self.tokens - DispatchQueue.global().async { - var amount = 0.0 - tokens.forEach { (token) in - guard timestamp == self.tokenListTimestamp else { return } - do { - try token.refreshBalance() - token.price = self.getPrice(token: token) - - if let price = token.price, let balance = token.balance { - amount += price * balance - } - DispatchQueue.main.async { - self.delegate?.walletPresenter(presenter: self, didRefreshTokenAmount: token) - } - } catch { - } - } - guard timestamp == self.tokenListTimestamp else { return } - DispatchQueue.main.async { - self.refreshing = false - self.delegate?.walletPresenterEndedRefresh(presenter: self) - self.delegate?.walletPresenter(presenter: self, didRefreshTotalAmount: amount) - } - } + refreshTokenListBalance(tokens: self.tokens) } @objc func refreshPrice() { @@ -132,7 +108,7 @@ class WalletPresenter { } // MARK: - Utils - func getPrice(token: Token) -> Double? { + private func getPrice(token: Token) -> Double? { let currencyToken = CurrencyService().searchCurrencyId(for: token.symbol) guard let tokenId = currencyToken?.id else { return nil @@ -151,7 +127,7 @@ class WalletPresenter { } extension WalletPresenter { - func observeCurrentWallet() { + private func observeCurrentWallet() { walletObserver = WalletRealmTool.getCurrentAppModel().observe { [weak self](change) in switch change { case .change(let propertys): @@ -164,57 +140,79 @@ extension WalletPresenter { } } - func observeWalletSelectTokenList() { + private func observeWalletSelectTokenList() { guard let wallet = currentWallet else { return } selectTokenListObserver?.invalidate() selectTokenListObserver = wallet.selectTokenList.observe({ [weak self](change) in guard let self = self else { return } - switch change { - case .update(let tokenList, let deletions, let insertions, modifications: _): - guard deletions.count > 0 || insertions.count > 0 else { return } - var tokens = self.tokens - let selectTokenFirstIndex = tokens.count - tokenList.count - deletions.count - deletions.enumerated().forEach({ (offset, element) in - let index = selectTokenFirstIndex + element - offset - tokens.remove(at: index) - }) - insertions.forEach({ (idx) in - let token = Token(tokenList[idx]) - token.walletAddress = self.currentWallet!.address - tokens.append(token) - }) - self.tokens = tokens - self.tokenListTimestamp = Date().timeIntervalSince1970 - self.refreshBalance() - default: - break - } + self.tokenListChangeHandler(change: change) }) } - func observeNativeTokenList() { + private func observeNativeTokenList() { nativeTokenListObserver?.invalidate() nativeTokenListObserver = WalletRealmTool.getCurrentAppModel().nativeTokenList.observe { [weak self](change) in guard let self = self else { return } - switch change { - case .update(let tokenList, let deletions, let insertions, modifications: _): - guard deletions.count > 0 || insertions.count > 0 else { return } - var tokens = self.tokens + self.tokenListChangeHandler(change: change) + } + } +} + +extension WalletPresenter { + private func tokenListChangeHandler(change: RealmCollectionChange>) { + switch change { + case .update(let tokenList, let deletions, let insertions, modifications: _): + guard deletions.count > 0 || insertions.count > 0 else { return } + if deletions.count > 0 { + var newTokens = tokens deletions.enumerated().forEach({ (offset, element) in let index = element - offset - tokens.remove(at: index) + newTokens.remove(at: index) }) - insertions.forEach({ (idx) in + tokens = newTokens + } + if insertions.count > 0 { + let newTokens = insertions.map({ (idx) -> Token in let token = Token(tokenList[idx]) - token.walletAddress = self.currentWallet!.address - tokens.append(token) + token.walletAddress = currentWallet!.address + return token }) + tokens += tokens + refreshTokenListBalance(tokens: newTokens) + } + default: + break + } + } - self.tokens = tokens - self.tokenListTimestamp = Date().timeIntervalSince1970 - self.refreshBalance() - default: - break + private func refreshTokenListBalance(tokens: [Token]) { + guard tokens.count > 0 else { return } + refreshing = true + delegate?.walletPresenterBeganRefresh(presenter: self) + let timestamp = self.tokenListTimestamp + DispatchQueue.global().async { + tokens.forEach { (token) in + guard self.tokens.contains(where: { $0 == token }) else { return } + do { + try token.refreshBalance() + token.price = self.getPrice(token: token) + DispatchQueue.main.async { + self.delegate?.walletPresenter(presenter: self, didRefreshTokenAmount: token) + } + } catch { + } + } + guard timestamp == self.tokenListTimestamp else { return } + var amount = 0.0 + self.tokens.forEach({ (token) in + if let price = token.price, let balance = token.balance { + amount += price * balance + } + }) + DispatchQueue.main.async { + self.refreshing = false + self.delegate?.walletPresenterEndedRefresh(presenter: self) + self.delegate?.walletPresenter(presenter: self, didRefreshTotalAmount: amount) } } } From 2db4f64d0df4cd805118ca2d151a98b9feb4ba81 Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 22 Nov 2018 11:05:19 +0800 Subject: [PATCH 152/315] Fix wallet issue --- .../Wallet/Home/WalletPresenter.swift | 97 ++++++++++--------- 1 file changed, 51 insertions(+), 46 deletions(-) diff --git a/Neuron/Sections/Wallet/Home/WalletPresenter.swift b/Neuron/Sections/Wallet/Home/WalletPresenter.swift index 757db88c..66345a77 100644 --- a/Neuron/Sections/Wallet/Home/WalletPresenter.swift +++ b/Neuron/Sections/Wallet/Home/WalletPresenter.swift @@ -81,51 +81,9 @@ class WalletPresenter { delegate?.walletPresenter(presenter: self, didRefreshCurrency: currency) refreshTokenListBalance(tokens: self.tokens) } - - @objc func refreshPrice() { - self.currency = LocalCurrencyService.shared.getLocalCurrencySelect() - delegate?.walletPresenterBeganRefresh(presenter: self) - let tokens = self.tokens - var amount = 0.0 - let currency = self.currency - DispatchQueue.global().async { - tokens.forEach { (token) in - token.price = self.getPrice(token: token) - if let price = token.price, let balance = token.balance { - amount += price * balance - } - DispatchQueue.main.async { - self.delegate?.walletPresenter(presenter: self, didRefreshTokenAmount: token) - } - } - guard currency?.short == self.currency.short else { return } - DispatchQueue.main.async { - self.refreshing = false - self.delegate?.walletPresenterEndedRefresh(presenter: self) - self.delegate?.walletPresenter(presenter: self, didRefreshTotalAmount: amount) - } - } - } - - // MARK: - Utils - private func getPrice(token: Token) -> Double? { - let currencyToken = CurrencyService().searchCurrencyId(for: token.symbol) - guard let tokenId = currencyToken?.id else { - return nil - } - return try? Promise.init { (resolver) in - CurrencyService().getCurrencyPrice(tokenid: tokenId, currencyType: currency.short, completion: { (result) in - switch result { - case .success(let price): - resolver.fulfill(price) - case .error(let error): - resolver.reject(error) - } - }) - }.wait() - } } +// MARK: - Observer extension WalletPresenter { private func observeCurrentWallet() { walletObserver = WalletRealmTool.getCurrentAppModel().observe { [weak self](change) in @@ -177,7 +135,7 @@ extension WalletPresenter { token.walletAddress = currentWallet!.address return token }) - tokens += tokens + tokens += newTokens refreshTokenListBalance(tokens: newTokens) } default: @@ -195,14 +153,13 @@ extension WalletPresenter { guard self.tokens.contains(where: { $0 == token }) else { return } do { try token.refreshBalance() - token.price = self.getPrice(token: token) + token.price = self.getTokenPrice(token: token) DispatchQueue.main.async { self.delegate?.walletPresenter(presenter: self, didRefreshTokenAmount: token) } } catch { } } - guard timestamp == self.tokenListTimestamp else { return } var amount = 0.0 self.tokens.forEach({ (token) in if let price = token.price, let balance = token.balance { @@ -210,6 +167,34 @@ extension WalletPresenter { } }) DispatchQueue.main.async { + guard timestamp == self.tokenListTimestamp else { return } + self.refreshing = false + self.delegate?.walletPresenterEndedRefresh(presenter: self) + self.delegate?.walletPresenter(presenter: self, didRefreshTotalAmount: amount) + } + } + } + + @objc func refreshPrice() { + self.currency = LocalCurrencyService.shared.getLocalCurrencySelect() + delegate?.walletPresenterBeganRefresh(presenter: self) + let tokens = self.tokens + let currency = self.currency + let timestamp = self.tokenListTimestamp + DispatchQueue.global().async { + var amount = 0.0 + tokens.forEach { (token) in + token.price = self.getTokenPrice(token: token) + if let price = token.price, let balance = token.balance { + amount += price * balance + } + DispatchQueue.main.async { + self.delegate?.walletPresenter(presenter: self, didRefreshTokenAmount: token) + } + } + DispatchQueue.main.async { + guard currency?.short == self.currency.short else { return } + guard timestamp == self.tokenListTimestamp else { return } self.refreshing = false self.delegate?.walletPresenterEndedRefresh(presenter: self) self.delegate?.walletPresenter(presenter: self, didRefreshTotalAmount: amount) @@ -217,3 +202,23 @@ extension WalletPresenter { } } } + +// MARK: - Utils +extension WalletPresenter { + private func getTokenPrice(token: Token) -> Double? { + let currencyToken = CurrencyService().searchCurrencyId(for: token.symbol) + guard let tokenId = currencyToken?.id else { + return nil + } + return try? Promise.init { (resolver) in + CurrencyService().getCurrencyPrice(tokenid: tokenId, currencyType: currency.short, completion: { (result) in + switch result { + case .success(let price): + resolver.fulfill(price) + case .error(let error): + resolver.reject(error) + } + }) + }.wait() + } +} From faaaed8f31c7f8ed8a448c409ce7f2e2cf15c9f9 Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 22 Nov 2018 11:09:26 +0800 Subject: [PATCH 153/315] Move `TransactionDetails` class to Models and remove `TransactionHistoryService` class --- .../TransactionDetails.swift | 0 Neuron/Models/TransactionModel.swift | 121 --------- .../Common/TransactionHistoryService.swift | 245 ------------------ 3 files changed, 366 deletions(-) rename Neuron/{Services/Common => Models}/TransactionDetails.swift (100%) delete mode 100644 Neuron/Models/TransactionModel.swift delete mode 100644 Neuron/Services/Common/TransactionHistoryService.swift diff --git a/Neuron/Services/Common/TransactionDetails.swift b/Neuron/Models/TransactionDetails.swift similarity index 100% rename from Neuron/Services/Common/TransactionDetails.swift rename to Neuron/Models/TransactionDetails.swift diff --git a/Neuron/Models/TransactionModel.swift b/Neuron/Models/TransactionModel.swift deleted file mode 100644 index a2f2afe5..00000000 --- a/Neuron/Models/TransactionModel.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// TransactionModel.swift -// Neuron -// -// Created by XiaoLu on 2018/7/9. -// Copyright © 2018年 cryptape. All rights reserved. -// - -import UIKit - -enum TransactionType: String { - case ETH - case ERC20 - case AppChain - case AppChainERC20 -} - -class TransactionModel: NSObject, Decodable { - var value = "" - var from = "" - var to = "" - var hashString = "" - var time: Date? - var chainName = "" - var gasUsed = "" - var gas = "" - var gasPrice = "" - var blockNumber = "" - var symbol = "" - var transactionType = TransactionType.ETH.rawValue //default "ETH" include ERC20 transaction, another one is "Nervos" - var totleGas = "" - - var chainId = "" - var formatTime = "" - - enum CodingKeys: String, CodingKey { - case from - case to - case hashString = "hash" - case timeStamp - case timestamp - case gasPrice - case gas - case gasUsed - case blockNumber - case value - case chainName - case chainId - case tokenSymbol - case tokenName - } - - override init() { - super.init() - } - - required init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - from = try values.decode(String.self, forKey: .from) - to = try values.decode(String.self, forKey: .to) - hashString = try values.decode(String.self, forKey: .hashString) - gasPrice = (try? values.decode(String.self, forKey: .gasPrice)) ?? "" - gas = (try? values.decode(String.self, forKey: .gas)) ?? "" - gasUsed = (try? values.decode(String.self, forKey: .gasUsed)) ?? "" - blockNumber = try values.decode(String.self, forKey: .blockNumber) - - if let string = try? values.decode(String.self, forKey: .value) { - value = string - } else if let number = try? values.decode(UInt.self, forKey: .value) { - value = "\(number)" - } - - if let value = try? values.decode(String.self, forKey: .chainName) { - chainName = value - } else if let value = try? values.decode(String.self, forKey: .tokenName) { - chainName = value - } - - if let value = try? values.decode(String.self, forKey: .tokenSymbol) { - symbol = value - } - - if let timestamp = try? values.decode(Int.self, forKey: .timestamp) { - time = Date(timeIntervalSince1970: Double(timestamp) / 1000.0) - } else if let string = try? values.decode(String.self, forKey: .timeStamp) { - if let timestamp = Double(string) { - time = Date(timeIntervalSince1970: timestamp) - } - } - if let date = time { - let dateformatter = DateFormatter() - dateformatter.dateFormat = "yyyy/MM/dd HH:mm:ss" - formatTime = dateformatter.string(from: date) - } - - let i_chainId = (try? values.decode(Int.self, forKey: .chainId)) ?? 0 - chainId = "\(i_chainId)" - } -} - -struct TransactionResponse: Decodable { - let result: Result - struct Result: Decodable { - let count: UInt - let transactions: [TransactionModel] - } -} - -struct NervosErc20TransactionResponse: Decodable { - let result: Result - struct Result: Decodable { - let count: UInt - let transfers: [TransactionModel] - } -} - -struct Erc20TransactionResponse: Decodable { - let status: String - let message: String - let result: [TransactionModel] -} diff --git a/Neuron/Services/Common/TransactionHistoryService.swift b/Neuron/Services/Common/TransactionHistoryService.swift deleted file mode 100644 index e8fe0211..00000000 --- a/Neuron/Services/Common/TransactionHistoryService.swift +++ /dev/null @@ -1,245 +0,0 @@ -// -// TransactionHistoryService.swift -// Neuron -// -// Created by 晨风 on 2018/10/22. -// Copyright © 2018 Cryptape. All rights reserved. -// - -import Foundation -import Alamofire -import BigInt -import Web3swift -import EthereumAddress -import AppChain - -class TransactionHistoryService { - private let ethereumSymbol = " ETH" - var transactions = [TransactionModel]() - var quotaPrice: Double = pow(10, 9) - var loading = false - private var page = 1 - private let pageSize = 20 - - var walletAddress: String { - return WalletRealmTool.getCurrentAppModel().currentWallet!.address - } - - let token: TokenModel - - fileprivate init(token: TokenModel) { - self.token = token - } - - static func service(with token: TokenModel) -> TransactionHistoryService { - if token.type == .erc20 { - return Erc20(token: token) - } else if token.type == .ether { - return Ethereum(token: token) - } else if token.type == .appChain { - return AppChain(token: token) - } else if token.type == .appChainErc20 { - return AppChainErc20(token: token) - } else { - fatalError() - } - } - - func reloadData(completion: @escaping (Error?) -> Void) { - guard loading == false else { return } - page = 1 - transactions = [] - loadMoreDate { (_, error) in - completion(error) - } - } - - func loadMoreDate(completion: @escaping ([Int], Error?) -> Void) { - guard loading == false else { return } - loading = true - loadData(page: page) { [weak self](transactions, error) in - guard let self = self else { return } - if let error = error { - completion([], error) - return - } - if transactions.count > 0 { - self.loading = false - self.page += 1 - } - var insertions = [Int]() - for idx in transactions.indices { - insertions.append(self.transactions.count + idx) - } - self.transactions.append(contentsOf: transactions) - completion(insertions, nil) - } - } - - func loadData(page: Int, completion: @escaping ([TransactionModel], Error?) -> Void) { - } - - func getAppChainQuotaPrice() { - // TODO: should use let but AppChain type conflicts with local AppChain... - var appChain = AppChainNetwork.appChain() - if let url = URL(string: token.chainHosts) { - appChain = AppChainNetwork.appChain(url: url) - } - do { - let quotaPrice = try Utils.getQuotaPrice(appChain: appChain) - self.quotaPrice = Double(quotaPrice) - } catch { - self.quotaPrice = pow(10, 9) - } - } -} - -extension TransactionHistoryService { - private class AppChain: TransactionHistoryService { - override init(token: TokenModel) { - super.init(token: token) - getAppChainQuotaPrice() - } - - override func loadData(page: Int, completion: @escaping ([TransactionModel], Error?) -> Void) { - let urlString = ServerApi.nervosTransactionURL + walletAddress.lowercased() - let parameters: [String: Any] = [ - "page": page, - "perPage": pageSize, - "valueFormat": "decimal" - ] - Alamofire.request(urlString, method: .get, parameters: parameters).responseJSON { [weak self](response) in - do { - guard let responseData = response.data else { throw TransactionError.requestfailed } - let nervosTransaction = try? JSONDecoder().decode(TransactionResponse.self, from: responseData) - guard let transactions = nervosTransaction?.result.transactions else { throw TransactionError.requestfailed } - var resultArr: [TransactionModel] = [] - guard let self = self else { throw TransactionError.requestfailed } - for transaction in transactions { - transaction.gasUsed = "\(Double(UInt.fromHex(transaction.gasUsed)) / pow(10, 18) * self.quotaPrice)" - transaction.blockNumber = "\(UInt.fromHex(transaction.blockNumber))" - transaction.transactionType = TransactionType.AppChain.rawValue - transaction.symbol = self.token.symbol - transaction.value = Web3.Utils.formatToEthereumUnits(BigUInt(atof(transaction.value)), toUnits: .eth, decimals: 8, fallbackToScientific: false)! - resultArr.append(transaction) - } - completion(transactions, nil) - } catch { - completion([], error) - } - } - } - - } - - private class Ethereum: TransactionHistoryService { - override func loadData(page: Int, completion: @escaping ([TransactionModel], Error?) -> Void) { - let url = EthereumNetwork().host().appendingPathComponent("/api") - let parameters: [String: Any] = [ - "apikey": ServerApi.etherScanKey, - "module": "account", - "action": "txlist", - "sort": "desc", - "address": walletAddress, - "page": page, - "offset": pageSize - ] - Alamofire.request(url, method: .get, parameters: parameters).responseJSON { [weak self](response) in - do { - guard let responseData = response.data else { throw TransactionError.requestfailed } - print(String(bytes: responseData.bytes, encoding: .utf8) ?? "none") - let ethTransaction = try? JSONDecoder().decode(Erc20TransactionResponse.self, from: responseData) - guard let transactions = ethTransaction?.result else { throw TransactionError.requestfailed } - var resultArr: [TransactionModel] = [] - guard let self = self else { throw TransactionError.requestfailed } - for transaction in transactions { - transaction.chainName = "Ethereum Mainnet" - let gasPriceBigNumber = BigUInt(Int(transaction.gasPrice)!) - transaction.gasPrice = Web3.Utils.formatToEthereumUnits(gasPriceBigNumber, toUnits: .Gwei, decimals: 8, fallbackToScientific: false) ?? "" - transaction.transactionType = TransactionType.ETH.rawValue - transaction.symbol = self.ethereumSymbol - let totleGasBigNumber = BigUInt(Double(transaction.gasUsed)! * Double(transaction.gasPrice)!) - transaction.totleGas = Web3.Utils.formatToEthereumUnits(totleGasBigNumber, toUnits: .eth, decimals: 8, fallbackToScientific: false) ?? "" - transaction.value = Web3.Utils.formatToEthereumUnits(BigUInt(atof(transaction.value)), toUnits: .eth, decimals: 8, fallbackToScientific: false)! - resultArr.append(transaction) - } - completion(resultArr, nil) - } catch { - completion([], error) - } - } - } - } - - private class Erc20: TransactionHistoryService { - override func loadData(page: Int, completion: @escaping ([TransactionModel], Error?) -> Void) { - let address = EthereumAddress.toChecksumAddress(token.address) ?? token.address - let parameters: [String: Any] = [ - "module": "account", - "action": "tokentx", - "contractaddress": address, - "address": walletAddress, - "page": page, - "offset": pageSize, - "sort": "desc", - "apikey": ServerApi.etherScanKey - ] - loading = true - Alamofire.request("https://api.etherscan.io/api", method: .get, parameters: parameters).responseData { [weak self](response) in - do { - guard let data = response.data, let self = self else { return } - let response = try JSONDecoder().decode(Erc20TransactionResponse.self, from: data) - var results = [TransactionModel]() - for transaction in response.result { - transaction.chainId = self.token.chainId - transaction.transactionType = TransactionType.ERC20.rawValue - let totleGasBigNumber = BigUInt(Int(transaction.gasUsed)! * Int(transaction.gasPrice)!) - transaction.totleGas = Web3.Utils.formatToEthereumUnits(totleGasBigNumber, toUnits: .eth, decimals: 8, fallbackToScientific: false) ?? "" - transaction.value = Web3.Utils.formatToEthereumUnits(BigUInt(atof(transaction.value)), toUnits: .eth, decimals: 8, fallbackToScientific: false)! - results.append(transaction) - } - completion(results, nil) - } catch { - completion([], error) - } - } - } - } - - private class AppChainErc20: TransactionHistoryService { - override init(token: TokenModel) { - super.init(token: token) - getAppChainQuotaPrice() - } - - override func loadData(page: Int, completion: @escaping ([TransactionModel], Error?) -> Void) { - let tokenAddress = token.address - let parameters: [String: Any] = [ - "address": tokenAddress, - "account": walletAddress, - "page": page, - "perPage": pageSize - ] - Alamofire.request("https://microscope.cryptape.com:8888/api/erc20/transfers", method: .get, parameters: parameters).responseData { [weak self](response) in - do { - guard let self = self else { throw TransactionError.requestfailed } - guard let responseData = response.data else { throw TransactionError.requestfailed } - print(String(bytes: responseData.bytes, encoding: .utf8)!) - let response = try JSONDecoder().decode(NervosErc20TransactionResponse.self, from: responseData) - var resultArr: [TransactionModel] = [] - for transaction in response.result.transfers { - transaction.gasUsed = "\(Double(UInt.fromHex(transaction.gasUsed)) / pow(10, 18) * self.quotaPrice)" - transaction.blockNumber = "\(UInt.fromHex(transaction.blockNumber))" - transaction.transactionType = TransactionType.AppChainERC20.rawValue - transaction.symbol = self.token.symbol - transaction.value = Web3.Utils.formatToEthereumUnits(BigUInt(atof(transaction.value)), toUnits: .eth, decimals: 8, fallbackToScientific: false)! - resultArr.append(transaction) - } - completion(resultArr, nil) - } catch { - completion([], error) - } - } - } - } -} From 38bc1d21f2b4a4e239ebd1f904870eac9a49558a Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 22 Nov 2018 12:10:06 +0900 Subject: [PATCH 154/315] Query token model in real time instead of holding the real object --- Neuron/Models/Token.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Neuron/Models/Token.swift b/Neuron/Models/Token.swift index 9c80000a..a046d837 100644 --- a/Neuron/Models/Token.swift +++ b/Neuron/Models/Token.swift @@ -29,22 +29,23 @@ class Token { var isNativeToken: Bool var walletAddress = "" var type: TokenType + let identifier: String + + var tokenModel: TokenModel { + let realm = RealmHelper().realm + return realm.object(ofType: TokenModel.self, forPrimaryKey: identifier)! + } private(set) var balance: Double? { didSet { - guard let realm = tokenModel.realm else { return } - DispatchQueue.main.async { - try? realm.write { - self.tokenModel.tokenBalance = self.balance ?? 0.0 - } + try? tokenModel.realm!.write { + tokenModel.tokenBalance = balance ?? 0.0 } } } var price: Double? - let tokenModel: TokenModel init(_ token: TokenModel) { - tokenModel = token name = token.name iconUrl = token.iconUrl address = token.address @@ -54,6 +55,7 @@ class Token { chainHosts = token.chainHosts isNativeToken = token.isNativeToken type = token.type + identifier = token.identifier } // MARK: - balance From b13eef499f3704df7299b17327dcd51eaf86274b Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 22 Nov 2018 11:10:49 +0800 Subject: [PATCH 155/315] Update project.pbxproj --- Neuron.xcodeproj/project.pbxproj | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 86b94bcc..889d7082 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -56,7 +56,6 @@ 6E83CB90216F573100029324 /* GuideCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E83CB8B216F573100029324 /* GuideCollectionViewCell.swift */; }; 6E83CB91216F573100029324 /* GuideViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E83CB8C216F573100029324 /* GuideViewController.swift */; }; 6E83CB93216F581800029324 /* ProductAgreement.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6E83CB92216F581800029324 /* ProductAgreement.txt */; }; - 6E851CD2217DBB3D00769E00 /* TransactionHistoryService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E851CD1217DBB3D00769E00 /* TransactionHistoryService.swift */; }; 6E859CB82176DC3100637E1C /* Overlay.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E859CB72176DC3100637E1C /* Overlay.storyboard */; }; 6E867E03216C3EC800BD6FE5 /* AuthSelectWalletViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E867E02216C3EC800BD6FE5 /* AuthSelectWalletViewController.swift */; }; 6E867E05216C3F1500BD6FE5 /* AuthPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E867E04216C3F1500BD6FE5 /* AuthPasswordViewController.swift */; }; @@ -162,7 +161,6 @@ 89A87C8B217312CC00588674 /* WalletNameValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89A87C8A217312CC00588674 /* WalletNameValidatorTests.swift */; }; 89AB35CA213D113E00A2BF48 /* WalletTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89AB35C9213D113E00A2BF48 /* WalletTableViewCell.swift */; }; 89B01A24216DAC5A00BFAAF4 /* DAppBrowser.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 89B01A23216DAC5A00BFAAF4 /* DAppBrowser.storyboard */; }; - 89B296EB20F4CB6B008558A7 /* TransactionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89B296EA20F4CB6B008558A7 /* TransactionModel.swift */; }; 89B296ED20F4CBA7008558A7 /* ServerAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89B296EC20F4CBA7008558A7 /* ServerAPI.swift */; }; 89B895B120F8B0CD00B9468B /* BrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89B895B020F8B0CD00B9468B /* BrowserViewController.swift */; }; 89BEBE6B20EF82B6007D2705 /* EthereumNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89BEBE6A20EF82B6007D2705 /* EthereumNetwork.swift */; }; @@ -272,7 +270,6 @@ 6E83CB8B216F573100029324 /* GuideCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GuideCollectionViewCell.swift; sourceTree = ""; }; 6E83CB8C216F573100029324 /* GuideViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GuideViewController.swift; sourceTree = ""; }; 6E83CB92216F581800029324 /* ProductAgreement.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = ProductAgreement.txt; sourceTree = ""; }; - 6E851CD1217DBB3D00769E00 /* TransactionHistoryService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistoryService.swift; sourceTree = ""; }; 6E859CB72176DC3100637E1C /* Overlay.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Overlay.storyboard; sourceTree = ""; }; 6E867E02216C3EC800BD6FE5 /* AuthSelectWalletViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthSelectWalletViewController.swift; sourceTree = ""; }; 6E867E04216C3F1500BD6FE5 /* AuthPasswordViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthPasswordViewController.swift; sourceTree = ""; }; @@ -386,7 +383,6 @@ 89A87C8A217312CC00588674 /* WalletNameValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletNameValidatorTests.swift; sourceTree = ""; }; 89AB35C9213D113E00A2BF48 /* WalletTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletTableViewCell.swift; sourceTree = ""; }; 89B01A23216DAC5A00BFAAF4 /* DAppBrowser.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = DAppBrowser.storyboard; sourceTree = ""; }; - 89B296EA20F4CB6B008558A7 /* TransactionModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionModel.swift; sourceTree = ""; }; 89B296EC20F4CBA7008558A7 /* ServerAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerAPI.swift; sourceTree = ""; }; 89B895B020F8B0CD00B9468B /* BrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserViewController.swift; sourceTree = ""; }; 89BEBE6A20EF82B6007D2705 /* EthereumNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumNetwork.swift; sourceTree = ""; }; @@ -696,8 +692,8 @@ isa = PBXGroup; children = ( 1A91E1E321A63561000EFD40 /* Realm */, - 89B296EA20F4CB6B008558A7 /* TransactionModel.swift */, 6E9B948021A408B400D67357 /* Token.swift */, + 6E7D3837219C173D0031177B /* TransactionDetails.swift */, ); path = Models; sourceTree = ""; @@ -1035,8 +1031,6 @@ 89CA1FAD213F832800669B2C /* CurrencyService.swift */, 89C6145D2141038300DC3DF4 /* LocalCurrencyService.swift */, 6E90D06821704C1D00B98363 /* SensorsAnalytics.swift */, - 6E851CD1217DBB3D00769E00 /* TransactionHistoryService.swift */, - 6E7D3837219C173D0031177B /* TransactionDetails.swift */, 6E3B2B6A219D938F0095257D /* SentTransaction.swift */, 6E3B2B74219EDE200095257D /* TransactionStatusManager.swift */, ); @@ -1403,7 +1397,6 @@ 8905660720D0AC120041D4B4 /* AppModel.swift in Sources */, 6E3B2B6D219ED9240095257D /* Ethereum+TransactionDetails.swift in Sources */, 6E9F9D56219AA71F009ED8B2 /* UINavigationBar+FixSpace.swift in Sources */, - 6E851CD2217DBB3D00769E00 /* TransactionHistoryService.swift in Sources */, 898A1A0F20B6539800ECB465 /* AddAssetTableViewCell.swift in Sources */, 6E9B947B21A4080300D67357 /* DesignableButton.swift in Sources */, 89CA1FAE213F832800669B2C /* CurrencyService.swift in Sources */, @@ -1512,7 +1505,6 @@ 8981212A218D3161000813C8 /* QRCodeViewController.swift in Sources */, 8964A18D20C0E4BE0086848F /* GenerateMnemonicController.swift in Sources */, 6EABFB0B21991BC200305ED5 /* DAppQRCodeMessageHandler.swift in Sources */, - 89B296EB20F4CB6B008558A7 /* TransactionModel.swift in Sources */, 1A6946602192A0B0000093A2 /* GasCalculator.swift in Sources */, 89D84CED2148C056006B0287 /* NFTFooterReusableView.swift in Sources */, 1A092245213580C900CAED5D /* NFTViewController.swift in Sources */, From 6387e209153dc6af23b484f4cf677db12a9f7d66 Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 22 Nov 2018 12:21:00 +0900 Subject: [PATCH 156/315] Update Realm schemaVersion --- Neuron/Utils/RealmHelper.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neuron/Utils/RealmHelper.swift b/Neuron/Utils/RealmHelper.swift index 453b21b3..61e51eb4 100644 --- a/Neuron/Utils/RealmHelper.swift +++ b/Neuron/Utils/RealmHelper.swift @@ -15,7 +15,7 @@ class RealmHelper { realm.autorefresh = true return realm } - private static var schemaVersion: UInt64 = 4 + private static var schemaVersion: UInt64 = 5 static func configureRealm() { var config = Realm.Configuration() From 71197a41f7e186fc91e2511ba4de004a4f79a3ea Mon Sep 17 00:00:00 2001 From: LuFP Date: Thu, 22 Nov 2018 11:46:23 +0800 Subject: [PATCH 157/315] Add CollectionTableViewCell --- Neuron.xcodeproj/project.pbxproj | 16 ++++ .../CollectionTableViewCell.swift | 15 +++ .../WebView/CollectionViewController.swift | 36 +++++++ .../Dapp/WebView/DAppBrowser.storyboard | 95 +++++++++++++++++++ .../Dapp/WebView/DappViewController.swift | 6 ++ 5 files changed, 168 insertions(+) create mode 100644 Neuron/Sections/Dapp/TableViewCell/CollectionTableViewCell.swift create mode 100644 Neuron/Sections/Dapp/WebView/CollectionViewController.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 84d9dd88..c66cf2dc 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -186,6 +186,8 @@ 89D84CEB2148C03B006B0287 /* NFTHeaderReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D84CEA2148C03B006B0287 /* NFTHeaderReusableView.swift */; }; 89D84CED2148C056006B0287 /* NFTFooterReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D84CEC2148C056006B0287 /* NFTFooterReusableView.swift */; }; 89D935252139363A002FAEEC /* TokenTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D935242139363A002FAEEC /* TokenTableViewCell.swift */; }; + 89D9418721A6576700F1B853 /* CollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D9418621A6576700F1B853 /* CollectionViewController.swift */; }; + 89D9418A21A65B6D00F1B853 /* CollectionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D9418921A65B6D00F1B853 /* CollectionTableViewCell.swift */; }; 89F7966D213A6B680064808A /* ERC721TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F7966C213A6B680064808A /* ERC721TableViewCell.swift */; }; 89F7966F213BD5680064808A /* Assets.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 89F7966E213BD5680064808A /* Assets.storyboard */; }; 89F79671213BD8C40064808A /* TransactionHistory.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 89F79670213BD8C40064808A /* TransactionHistory.storyboard */; }; @@ -408,6 +410,8 @@ 89D84CEA2148C03B006B0287 /* NFTHeaderReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFTHeaderReusableView.swift; sourceTree = ""; }; 89D84CEC2148C056006B0287 /* NFTFooterReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFTFooterReusableView.swift; sourceTree = ""; }; 89D935242139363A002FAEEC /* TokenTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenTableViewCell.swift; sourceTree = ""; }; + 89D9418621A6576700F1B853 /* CollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewController.swift; sourceTree = ""; }; + 89D9418921A65B6D00F1B853 /* CollectionTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTableViewCell.swift; sourceTree = ""; }; 89F7966C213A6B680064808A /* ERC721TableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ERC721TableViewCell.swift; sourceTree = ""; }; 89F7966E213BD5680064808A /* Assets.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Assets.storyboard; sourceTree = ""; }; 89F79670213BD8C40064808A /* TransactionHistory.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = TransactionHistory.storyboard; sourceTree = ""; }; @@ -655,6 +659,7 @@ 89B01A23216DAC5A00BFAAF4 /* DAppBrowser.storyboard */, 8928C24820AE91E600C3103E /* DappViewController.swift */, 8928C26920AED5C100C3103E /* SearchAppController.swift */, + 89D9418621A6576700F1B853 /* CollectionViewController.swift */, 89B895B020F8B0CD00B9468B /* BrowserViewController.swift */, 8935BA54216EE65500C37263 /* WKWebViewConfiguration.swift */, 89C25C6B20BD421E00007EC1 /* ContractController.swift */, @@ -770,6 +775,7 @@ 890E3112219BC56A00561F3D /* WebView */, 890E3113219BC56A00561F3D /* Handler */, 6EABFB0921991AF900305ED5 /* Native */, + 89D9418821A659FA00F1B853 /* TableViewCell */, ); path = Dapp; sourceTree = ""; @@ -1046,6 +1052,14 @@ path = WalletManager; sourceTree = ""; }; + 89D9418821A659FA00F1B853 /* TableViewCell */ = { + isa = PBXGroup; + children = ( + 89D9418921A65B6D00F1B853 /* CollectionTableViewCell.swift */, + ); + path = TableViewCell; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1420,6 +1434,7 @@ 6E876843218023520032EBCE /* CoinMarketCap.swift in Sources */, 6E9F9D58219AAF78009ED8B2 /* NSObject+Extension.swift in Sources */, 89A196192175D9C800BD5FA4 /* DAppDataHandle.swift in Sources */, + 89D9418721A6576700F1B853 /* CollectionViewController.swift in Sources */, 89D84CEB2148C03B006B0287 /* NFTHeaderReusableView.swift in Sources */, 8964A17D20BE7F750086848F /* MessageSignController.swift in Sources */, 6E867E11216CA51600BD6FE5 /* SettingCurrencyTableViewCell.swift in Sources */, @@ -1432,6 +1447,7 @@ 6E9D7CC5216B490D0044176D /* AuthDeviceViewController.swift in Sources */, 89F7966D213A6B680064808A /* ERC721TableViewCell.swift in Sources */, 8964A19320C115A70086848F /* VerifyMnemonicViewController.swift in Sources */, + 89D9418A21A65B6D00F1B853 /* CollectionTableViewCell.swift in Sources */, 898A1A0B20B64F6000ECB465 /* AddAssetController.swift in Sources */, 89F9E73E20DEBDE8009E68D4 /* ExportKeystoreController.swift in Sources */, 89D935252139363A002FAEEC /* TokenTableViewCell.swift in Sources */, diff --git a/Neuron/Sections/Dapp/TableViewCell/CollectionTableViewCell.swift b/Neuron/Sections/Dapp/TableViewCell/CollectionTableViewCell.swift new file mode 100644 index 00000000..577b8ad7 --- /dev/null +++ b/Neuron/Sections/Dapp/TableViewCell/CollectionTableViewCell.swift @@ -0,0 +1,15 @@ +// +// CollectionTableViewCell.swift +// Neuron +// +// Created by XiaoLu on 2018/11/22. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit + +class CollectionTableViewCell: UITableViewCell { + @IBOutlet weak var dappIconImageView: UIImageView! + @IBOutlet weak var dappNameLabel: UILabel! + @IBOutlet weak var timeLabel: UILabel! +} diff --git a/Neuron/Sections/Dapp/WebView/CollectionViewController.swift b/Neuron/Sections/Dapp/WebView/CollectionViewController.swift new file mode 100644 index 00000000..b8fa6eb2 --- /dev/null +++ b/Neuron/Sections/Dapp/WebView/CollectionViewController.swift @@ -0,0 +1,36 @@ +// +// CollectionViewController.swift +// Neuron +// +// Created by XiaoLu on 2018/11/22. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit + +class CollectionViewController: UITableViewController { + @IBOutlet weak var dappIconImageView: UIImageView! + @IBOutlet weak var dappNameLabel: UILabel! + @IBOutlet weak var timeLabel: UILabel! + + override func viewDidLoad() { + super.viewDidLoad() + + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 0 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return 0 + } + + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "collectionTableViewCell", for: indexPath) + + + return cell + } +} diff --git a/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard b/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard index f46b0f6e..3a7536a7 100644 --- a/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard +++ b/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard @@ -76,6 +76,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -729,6 +806,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/Neuron/Sections/Dapp/WebView/DappViewController.swift b/Neuron/Sections/Dapp/WebView/DappViewController.swift index e7ab8aa4..b0fcc3b9 100644 --- a/Neuron/Sections/Dapp/WebView/DappViewController.swift +++ b/Neuron/Sections/Dapp/WebView/DappViewController.swift @@ -49,6 +49,8 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, webView.configuration.userContentController.addUserScript(userScript) webView.configuration.preferences.javaScriptEnabled = true webView.configuration.userContentController.add(self, name: "pushSearchView") + webView.configuration.userContentController.add(self, name: "pushMyDAppView") + webView.configuration.userContentController.add(self, name: "pushCollectionView") webView.scrollView.showsHorizontalScrollIndicator = false webView.scrollView.showsVerticalScrollIndicator = false @@ -97,6 +99,10 @@ extension DappViewController: WKScriptMessageHandler { case "pushSearchView": let sCtrl = UIStoryboard(name: "DAppBrowser", bundle: nil).instantiateViewController(withIdentifier: "searchAppController") self.navigationController?.pushViewController(sCtrl, animated: true) + case "pushMyDAppView": + Toast.showToast(text: "敬请期待") + case "pushCollectionView": + Toast.showToast(text: "敬请期待") default: break } From cd0447dc9bd3ee6b78fc5b3a682d06632c1e37b2 Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 22 Nov 2018 13:48:33 +0900 Subject: [PATCH 158/315] Use standard tab bar item title position --- Neuron/MainViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Neuron/MainViewController.swift b/Neuron/MainViewController.swift index 60dce89c..6779b1c0 100644 --- a/Neuron/MainViewController.swift +++ b/Neuron/MainViewController.swift @@ -45,7 +45,6 @@ class MainViewController: UITabBarController, UITabBarControllerDelegate { } private func applyStyle() { - UITabBarItem.appearance().titlePositionAdjustment = UIOffset(horizontal: 0, vertical: -4) UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: AppColor.newThemeColor], for: .selected) let navigationBarBackImage = UIImage(named: "nav_darkback")!.withRenderingMode(.alwaysOriginal) From e2a33a973d17d61179316eafeb2738d87762289d Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 22 Nov 2018 14:28:10 +0800 Subject: [PATCH 159/315] Add default token mba --- Neuron/MainViewController.swift | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/Neuron/MainViewController.swift b/Neuron/MainViewController.swift index 60dce89c..a4ef4e79 100644 --- a/Neuron/MainViewController.swift +++ b/Neuron/MainViewController.swift @@ -7,6 +7,7 @@ // import UIKit +import RealmSwift class MainViewController: UITabBarController, UITabBarControllerDelegate { override func viewDidLoad() { @@ -42,6 +43,32 @@ class MainViewController: UITabBarController, UITabBarControllerDelegate { WalletRealmTool.addObject(appModel: appModel) } } + + // Add mba token + DispatchQueue.global().async { + let mbaHost = "http://testnet.mba.cmbchina.biz:1337" + do { + let metaData = try AppChainNetwork.appChain(url: URL(string: mbaHost)!).rpc.getMetaData() + let realm = try Realm() + let appModel = realm.objects(AppModel.self).first! + guard !appModel.nativeTokenList.contains(where: { + $0.chainId == metaData.chainId && $0.chainHosts == mbaHost && $0.symbol == metaData.tokenSymbol + }) else { return } + + let tokenModel = TokenModel() + tokenModel.chainId = metaData.chainId + tokenModel.chainName = metaData.chainName + tokenModel.symbol = metaData.tokenSymbol + tokenModel.iconUrl = metaData.tokenAvatar + tokenModel.name = metaData.tokenName + tokenModel.chainHosts = mbaHost + tokenModel.isNativeToken = true + try realm.write { + appModel.nativeTokenList.append(tokenModel) + } + } catch { + } + } } private func applyStyle() { From f6a6a91964019b86281327cda62291f09e2ef105 Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 22 Nov 2018 14:28:31 +0800 Subject: [PATCH 160/315] Fix wallet issue --- Neuron/Sections/Wallet/Home/WalletViewController.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Neuron/Sections/Wallet/Home/WalletViewController.swift b/Neuron/Sections/Wallet/Home/WalletViewController.swift index a991e863..4d6f0f22 100644 --- a/Neuron/Sections/Wallet/Home/WalletViewController.swift +++ b/Neuron/Sections/Wallet/Home/WalletViewController.swift @@ -18,13 +18,15 @@ class WalletViewController: UIViewController { @IBOutlet weak var currencyLabel: UILabel! @IBOutlet weak var totalAmountLabel: UILabel! - private var presenter = WalletPresenter() + private var presenter: WalletPresenter! private var walletCountObserve: NotificationToken? override func viewDidLoad() { super.viewDidLoad() let refresh = UIRefreshControl() refresh.addTarget(self, action: #selector(WalletViewController.refresh), for: .valueChanged) tableView.refreshControl = refresh + + presenter = WalletPresenter() presenter.delegate = self presenter.refresh() From 4be42a84ba12246dae05c868c9d28a781f4b365d Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 22 Nov 2018 14:29:02 +0800 Subject: [PATCH 161/315] Fix mba balance issue --- Neuron/Models/Token.swift | 2 +- Neuron/Services/AppChain/AppChainNetwork.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Neuron/Models/Token.swift b/Neuron/Models/Token.swift index a046d837..bf371d65 100644 --- a/Neuron/Models/Token.swift +++ b/Neuron/Models/Token.swift @@ -73,7 +73,7 @@ class Token { case .ether: balance = try EthereumNetwork().getWeb3().eth.getBalance(address: EthereumAddress(walletAddress)!) case .appChain, .appChainErc20: - balance = try AppChainNetwork.appChain().rpc.getBalance(address: walletAddress) + balance = try AppChainNetwork.appChain(url: URL(string: chainHosts)).rpc.getBalance(address: walletAddress) case .erc20: let contractAddress = EthereumAddress(address)! let walletAddress = EthereumAddress(self.walletAddress)! diff --git a/Neuron/Services/AppChain/AppChainNetwork.swift b/Neuron/Services/AppChain/AppChainNetwork.swift index e6d9853a..ce01c3cb 100644 --- a/Neuron/Services/AppChain/AppChainNetwork.swift +++ b/Neuron/Services/AppChain/AppChainNetwork.swift @@ -12,7 +12,8 @@ import AppChain struct AppChainNetwork { private static let defaultNode = "http://121.196.200.225:1337" - static func appChain(url: URL = URL(string: defaultNode)!) -> AppChain { + static func appChain(url: URL? = URL(string: defaultNode)!) -> AppChain { + let url = url == nil ? URL(string: defaultNode)! : url! return AppChain(provider: HTTPProvider(url)!) } From 2caa64217061338ecc1c37ed4de63bd9ed82f4b2 Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 22 Nov 2018 15:30:33 +0900 Subject: [PATCH 162/315] Use table view's default separator and group style --- Neuron.xcodeproj/project.pbxproj | 12 +- .../Launch Screen/Contents.json | 6 + .../launch_icon.imageset/Contents.json | 0 .../launch_icon.imageset/launchIcon@2x.png | Bin .../launch_icon.imageset/launchIcon@3x.png | Bin .../nervos_launch.imageset/Contents.json | 0 .../nervos_launch@2x.png | Bin .../nervos_launch@3x.png | Bin .../peckshield_launch.imageset}/Contents.json | 0 .../peckshield_launch.imageset}/Group@1x.png | Bin .../peckshield_launch.imageset}/Group@2x.png | Bin .../peckshield_launch.imageset}/Group@3x.png | Bin .../Contents.json | 0 .../openSea@2x.png | Bin .../openSea@3x.png | Bin .../1527734402443453.png | Bin .../Contents.json | 0 Neuron/Base.lproj/LaunchScreen.storyboard | 4 +- Neuron/Sections/Settings/AboutUs.storyboard | 277 ++++++++++++++++ ...ller.swift => AboutUsViewController.swift} | 8 +- Neuron/Sections/Settings/Settings.storyboard | 312 ++---------------- .../WalletDetails/WalletManagement.storyboard | 20 +- 22 files changed, 328 insertions(+), 311 deletions(-) create mode 100644 Neuron/Assets.xcassets/Launch Screen/Contents.json rename Neuron/Assets.xcassets/{Icons => Launch Screen}/launch_icon.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons => Launch Screen}/launch_icon.imageset/launchIcon@2x.png (100%) rename Neuron/Assets.xcassets/{Icons => Launch Screen}/launch_icon.imageset/launchIcon@3x.png (100%) rename Neuron/Assets.xcassets/{Icons => Launch Screen}/nervos_launch.imageset/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons => Launch Screen}/nervos_launch.imageset/nervos_launch@2x.png (100%) rename Neuron/Assets.xcassets/{Icons => Launch Screen}/nervos_launch.imageset/nervos_launch@3x.png (100%) rename Neuron/Assets.xcassets/{Icons/peckshield.imageset => Launch Screen/peckshield_launch.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/{Icons/peckshield.imageset => Launch Screen/peckshield_launch.imageset}/Group@1x.png (100%) rename Neuron/Assets.xcassets/{Icons/peckshield.imageset => Launch Screen/peckshield_launch.imageset}/Group@2x.png (100%) rename Neuron/Assets.xcassets/{Icons/peckshield.imageset => Launch Screen/peckshield_launch.imageset}/Group@3x.png (100%) rename Neuron/Assets.xcassets/Settings/{open_sea.imageset => opensea.imageset}/Contents.json (100%) rename Neuron/Assets.xcassets/Settings/{open_sea.imageset => opensea.imageset}/openSea@2x.png (100%) rename Neuron/Assets.xcassets/Settings/{open_sea.imageset => opensea.imageset}/openSea@3x.png (100%) rename Neuron/Assets.xcassets/Settings/{peck_shield.imageset => peckshield.imageset}/1527734402443453.png (100%) rename Neuron/Assets.xcassets/Settings/{peck_shield.imageset => peckshield.imageset}/Contents.json (100%) create mode 100644 Neuron/Sections/Settings/AboutUs.storyboard rename Neuron/Sections/Settings/{AboutUsTableViewController.swift => AboutUsViewController.swift} (94%) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 889d7082..7865a464 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ 1AE3145121A2F5A300F92B2A /* TransactionParamBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AE3145021A2F5A300F92B2A /* TransactionParamBuilder.swift */; }; 1AEBF3E4219BA1E700653FF5 /* BigUInt+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AEBF3E3219BA1E700653FF5 /* BigUInt+Extension.swift */; }; 1AEBF3E6219BA22600653FF5 /* BigUIntExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1AEBF3E5219BA22600653FF5 /* BigUIntExtensionTests.swift */; }; + 1AEF127C21A677B600202373 /* AboutUs.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1AEF127B21A677B600202373 /* AboutUs.storyboard */; }; 3B9D2E9C9E858D6634948E6D /* Pods_Neuron.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8CC6FD685582D038A3767657 /* Pods_Neuron.framework */; }; 6E3B2B6B219D938F0095257D /* SentTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3B2B6A219D938F0095257D /* SentTransaction.swift */; }; 6E3B2B6D219ED9240095257D /* Ethereum+TransactionDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3B2B6C219ED9230095257D /* Ethereum+TransactionDetails.swift */; }; @@ -176,7 +177,7 @@ 89C6E09920E07F8E00C94026 /* ShareItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C6E09820E07F8E00C94026 /* ShareItem.swift */; }; 89CA1FAC213F822B00669B2C /* tokens-list.json in Resources */ = {isa = PBXBuildFile; fileRef = 89CA1FAB213F822B00669B2C /* tokens-list.json */; }; 89CA1FAE213F832800669B2C /* CurrencyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CA1FAD213F832800669B2C /* CurrencyService.swift */; }; - 89CFA12621424CAB00635A54 /* AboutUsTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CFA12521424CAB00635A54 /* AboutUsTableViewController.swift */; }; + 89CFA12621424CAB00635A54 /* AboutUsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CFA12521424CAB00635A54 /* AboutUsViewController.swift */; }; 89D4AC2E20D59F270097E02B /* WalletManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D4AC2520D59F270097E02B /* WalletManager.swift */; }; 89D4AC3620D8AFC50097E02B /* WalletRealmTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D4AC3520D8AFC50097E02B /* WalletRealmTool.swift */; }; 89D4AC3820D8DB610097E02B /* NotificationName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D4AC3720D8DB610097E02B /* NotificationName.swift */; }; @@ -251,6 +252,7 @@ 1AE3145021A2F5A300F92B2A /* TransactionParamBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionParamBuilder.swift; sourceTree = ""; }; 1AEBF3E3219BA1E700653FF5 /* BigUInt+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "BigUInt+Extension.swift"; sourceTree = ""; }; 1AEBF3E5219BA22600653FF5 /* BigUIntExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BigUIntExtensionTests.swift; sourceTree = ""; }; + 1AEF127B21A677B600202373 /* AboutUs.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = AboutUs.storyboard; sourceTree = ""; }; 1D270F090888C6AD9CF6A055 /* Pods-Neuron.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Neuron.release.xcconfig"; path = "Pods/Target Support Files/Pods-Neuron/Pods-Neuron.release.xcconfig"; sourceTree = ""; }; 21C5F1D714208410CA8D77B9 /* Pods-Neuron.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Neuron.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Neuron/Pods-Neuron.debug.xcconfig"; sourceTree = ""; }; 3502E1744CFA926363F980D0 /* Pods_NeuronUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NeuronUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -398,7 +400,7 @@ 89C6E09820E07F8E00C94026 /* ShareItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareItem.swift; sourceTree = ""; }; 89CA1FAB213F822B00669B2C /* tokens-list.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "tokens-list.json"; sourceTree = ""; }; 89CA1FAD213F832800669B2C /* CurrencyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyService.swift; sourceTree = ""; }; - 89CFA12521424CAB00635A54 /* AboutUsTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutUsTableViewController.swift; sourceTree = ""; }; + 89CFA12521424CAB00635A54 /* AboutUsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutUsViewController.swift; sourceTree = ""; }; 89D40EF520DCF1E00062C545 /* Neuron.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Neuron.entitlements; sourceTree = ""; }; 89D4AC2520D59F270097E02B /* WalletManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletManager.swift; sourceTree = ""; }; 89D4AC3520D8AFC50097E02B /* WalletRealmTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletRealmTool.swift; sourceTree = ""; }; @@ -714,9 +716,10 @@ children = ( 1A2BFA1721338FEB0065496B /* Settings.storyboard */, 89C25C5820BBA38300007EC1 /* SettingsViewController.swift */, + 1AEF127B21A677B600202373 /* AboutUs.storyboard */, + 89CFA12521424CAB00635A54 /* AboutUsViewController.swift */, 89BEBE7120F12647007D2705 /* CommonWebViewController.swift */, 897AFC592130129500383A12 /* CurrencyViewController.swift */, - 89CFA12521424CAB00635A54 /* AboutUsTableViewController.swift */, 8989E2CE21883400008A1BDE /* SwitchNetworkViewController.swift */, 8922685920DE4EA50021A85F /* TableViewCell */, ); @@ -1190,6 +1193,7 @@ 6E859CB82176DC3100637E1C /* Overlay.storyboard in Resources */, 898CA98A20EA2E770059ECA3 /* tokens-eth.json in Resources */, 89CA1FAC213F822B00669B2C /* tokens-list.json in Resources */, + 1AEF127C21A677B600202373 /* AboutUs.storyboard in Resources */, 6E83CB8F216F573100029324 /* Guide.storyboard in Resources */, 89C3BF072134E3F200872BD0 /* AddWallet.storyboard in Resources */, 1A5515EA218D3F0300D34791 /* Localizable.strings in Resources */, @@ -1498,7 +1502,7 @@ 6E8173B3218D55A10078A2CF /* InputTextViewController.swift in Sources */, 89C6145E2141038300DC3DF4 /* LocalCurrencyService.swift in Sources */, 8928C23520AE8F7C00C3103E /* UIStyle.swift in Sources */, - 89CFA12621424CAB00635A54 /* AboutUsTableViewController.swift in Sources */, + 89CFA12621424CAB00635A54 /* AboutUsViewController.swift in Sources */, 6E3B2B71219EDA230095257D /* AppChain+TransactionDetails.swift in Sources */, 1A69465E21916C3D000093A2 /* AppChainTxSender.swift in Sources */, 8928C26B20AED5C100C3103E /* SearchAppController.swift in Sources */, diff --git a/Neuron/Assets.xcassets/Launch Screen/Contents.json b/Neuron/Assets.xcassets/Launch Screen/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Neuron/Assets.xcassets/Launch Screen/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Icons/launch_icon.imageset/Contents.json b/Neuron/Assets.xcassets/Launch Screen/launch_icon.imageset/Contents.json similarity index 100% rename from Neuron/Assets.xcassets/Icons/launch_icon.imageset/Contents.json rename to Neuron/Assets.xcassets/Launch Screen/launch_icon.imageset/Contents.json diff --git a/Neuron/Assets.xcassets/Icons/launch_icon.imageset/launchIcon@2x.png b/Neuron/Assets.xcassets/Launch Screen/launch_icon.imageset/launchIcon@2x.png similarity index 100% rename from Neuron/Assets.xcassets/Icons/launch_icon.imageset/launchIcon@2x.png rename to Neuron/Assets.xcassets/Launch Screen/launch_icon.imageset/launchIcon@2x.png diff --git a/Neuron/Assets.xcassets/Icons/launch_icon.imageset/launchIcon@3x.png b/Neuron/Assets.xcassets/Launch Screen/launch_icon.imageset/launchIcon@3x.png similarity index 100% rename from Neuron/Assets.xcassets/Icons/launch_icon.imageset/launchIcon@3x.png rename to Neuron/Assets.xcassets/Launch Screen/launch_icon.imageset/launchIcon@3x.png diff --git a/Neuron/Assets.xcassets/Icons/nervos_launch.imageset/Contents.json b/Neuron/Assets.xcassets/Launch Screen/nervos_launch.imageset/Contents.json similarity index 100% rename from Neuron/Assets.xcassets/Icons/nervos_launch.imageset/Contents.json rename to Neuron/Assets.xcassets/Launch Screen/nervos_launch.imageset/Contents.json diff --git a/Neuron/Assets.xcassets/Icons/nervos_launch.imageset/nervos_launch@2x.png b/Neuron/Assets.xcassets/Launch Screen/nervos_launch.imageset/nervos_launch@2x.png similarity index 100% rename from Neuron/Assets.xcassets/Icons/nervos_launch.imageset/nervos_launch@2x.png rename to Neuron/Assets.xcassets/Launch Screen/nervos_launch.imageset/nervos_launch@2x.png diff --git a/Neuron/Assets.xcassets/Icons/nervos_launch.imageset/nervos_launch@3x.png b/Neuron/Assets.xcassets/Launch Screen/nervos_launch.imageset/nervos_launch@3x.png similarity index 100% rename from Neuron/Assets.xcassets/Icons/nervos_launch.imageset/nervos_launch@3x.png rename to Neuron/Assets.xcassets/Launch Screen/nervos_launch.imageset/nervos_launch@3x.png diff --git a/Neuron/Assets.xcassets/Icons/peckshield.imageset/Contents.json b/Neuron/Assets.xcassets/Launch Screen/peckshield_launch.imageset/Contents.json similarity index 100% rename from Neuron/Assets.xcassets/Icons/peckshield.imageset/Contents.json rename to Neuron/Assets.xcassets/Launch Screen/peckshield_launch.imageset/Contents.json diff --git a/Neuron/Assets.xcassets/Icons/peckshield.imageset/Group@1x.png b/Neuron/Assets.xcassets/Launch Screen/peckshield_launch.imageset/Group@1x.png similarity index 100% rename from Neuron/Assets.xcassets/Icons/peckshield.imageset/Group@1x.png rename to Neuron/Assets.xcassets/Launch Screen/peckshield_launch.imageset/Group@1x.png diff --git a/Neuron/Assets.xcassets/Icons/peckshield.imageset/Group@2x.png b/Neuron/Assets.xcassets/Launch Screen/peckshield_launch.imageset/Group@2x.png similarity index 100% rename from Neuron/Assets.xcassets/Icons/peckshield.imageset/Group@2x.png rename to Neuron/Assets.xcassets/Launch Screen/peckshield_launch.imageset/Group@2x.png diff --git a/Neuron/Assets.xcassets/Icons/peckshield.imageset/Group@3x.png b/Neuron/Assets.xcassets/Launch Screen/peckshield_launch.imageset/Group@3x.png similarity index 100% rename from Neuron/Assets.xcassets/Icons/peckshield.imageset/Group@3x.png rename to Neuron/Assets.xcassets/Launch Screen/peckshield_launch.imageset/Group@3x.png diff --git a/Neuron/Assets.xcassets/Settings/open_sea.imageset/Contents.json b/Neuron/Assets.xcassets/Settings/opensea.imageset/Contents.json similarity index 100% rename from Neuron/Assets.xcassets/Settings/open_sea.imageset/Contents.json rename to Neuron/Assets.xcassets/Settings/opensea.imageset/Contents.json diff --git a/Neuron/Assets.xcassets/Settings/open_sea.imageset/openSea@2x.png b/Neuron/Assets.xcassets/Settings/opensea.imageset/openSea@2x.png similarity index 100% rename from Neuron/Assets.xcassets/Settings/open_sea.imageset/openSea@2x.png rename to Neuron/Assets.xcassets/Settings/opensea.imageset/openSea@2x.png diff --git a/Neuron/Assets.xcassets/Settings/open_sea.imageset/openSea@3x.png b/Neuron/Assets.xcassets/Settings/opensea.imageset/openSea@3x.png similarity index 100% rename from Neuron/Assets.xcassets/Settings/open_sea.imageset/openSea@3x.png rename to Neuron/Assets.xcassets/Settings/opensea.imageset/openSea@3x.png diff --git a/Neuron/Assets.xcassets/Settings/peck_shield.imageset/1527734402443453.png b/Neuron/Assets.xcassets/Settings/peckshield.imageset/1527734402443453.png similarity index 100% rename from Neuron/Assets.xcassets/Settings/peck_shield.imageset/1527734402443453.png rename to Neuron/Assets.xcassets/Settings/peckshield.imageset/1527734402443453.png diff --git a/Neuron/Assets.xcassets/Settings/peck_shield.imageset/Contents.json b/Neuron/Assets.xcassets/Settings/peckshield.imageset/Contents.json similarity index 100% rename from Neuron/Assets.xcassets/Settings/peck_shield.imageset/Contents.json rename to Neuron/Assets.xcassets/Settings/peckshield.imageset/Contents.json diff --git a/Neuron/Base.lproj/LaunchScreen.storyboard b/Neuron/Base.lproj/LaunchScreen.storyboard index 7558cf37..7de8af88 100644 --- a/Neuron/Base.lproj/LaunchScreen.storyboard +++ b/Neuron/Base.lproj/LaunchScreen.storyboard @@ -32,7 +32,7 @@ - + @@ -60,6 +60,6 @@ - + diff --git a/Neuron/Sections/Settings/AboutUs.storyboard b/Neuron/Sections/Settings/AboutUs.storyboard new file mode 100644 index 00000000..3b0069e2 --- /dev/null +++ b/Neuron/Sections/Settings/AboutUs.storyboard @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Neuron/Sections/Settings/AboutUsTableViewController.swift b/Neuron/Sections/Settings/AboutUsViewController.swift similarity index 94% rename from Neuron/Sections/Settings/AboutUsTableViewController.swift rename to Neuron/Sections/Settings/AboutUsViewController.swift index 764bdcb0..d51617d2 100644 --- a/Neuron/Sections/Settings/AboutUsTableViewController.swift +++ b/Neuron/Sections/Settings/AboutUsViewController.swift @@ -1,5 +1,5 @@ // -// AboutUsTableViewController.swift +// AboutUsViewController.swift // Neuron // // Created by XiaoLu on 2018/9/7. @@ -9,7 +9,7 @@ import UIKit import SafariServices -class AboutUsTableViewController: UITableViewController { +class AboutUsViewController: UITableViewController { @IBOutlet weak var versionLabel: UILabel! override func viewDidLoad() { @@ -46,7 +46,9 @@ class AboutUsTableViewController: UITableViewController { let bundleName = Bundle.main.infoDictionary!["CFBundleName"] as? String ?? "Info.plist" if let infoPath = Bundle.main.path(forResource: bundleName, ofType: nil), let infoAttr = try? FileManager.default.attributesOfItem(atPath: infoPath), - let infoDate = infoAttr[FileAttributeKey.creationDate] as? Date { return infoDate } + let infoDate = infoAttr[FileAttributeKey.creationDate] as? Date { + return infoDate + } return Date() } diff --git a/Neuron/Sections/Settings/Settings.storyboard b/Neuron/Sections/Settings/Settings.storyboard index 20876f08..c2dcf8ad 100644 --- a/Neuron/Sections/Settings/Settings.storyboard +++ b/Neuron/Sections/Settings/Settings.storyboard @@ -14,21 +14,15 @@ - + - - - - - - - + @@ -85,7 +79,7 @@ - + @@ -116,7 +110,7 @@ - + @@ -140,7 +134,7 @@ - + @@ -175,7 +169,7 @@ - + @@ -195,11 +189,11 @@ - + - + @@ -220,7 +214,7 @@ - + @@ -283,7 +277,7 @@ - + @@ -293,7 +287,7 @@ - + @@ -303,7 +297,7 @@ - + @@ -354,7 +348,7 @@ - + @@ -364,7 +358,7 @@ - + @@ -418,267 +412,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -701,24 +435,24 @@ + + + + + + + + - - - - - - - - diff --git a/Neuron/Sections/Wallet/WalletDetails/WalletManagement.storyboard b/Neuron/Sections/Wallet/WalletDetails/WalletManagement.storyboard index da922814..403fc980 100644 --- a/Neuron/Sections/Wallet/WalletDetails/WalletManagement.storyboard +++ b/Neuron/Sections/Wallet/WalletDetails/WalletManagement.storyboard @@ -14,18 +14,12 @@ - + - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -558,7 +417,6 @@ - From 1b65204e841a20c7a94ea987afa04b7ddace67c7 Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 22 Nov 2018 15:36:36 +0900 Subject: [PATCH 164/315] Set selection style to none for info only table view cells --- Neuron/Sections/Settings/AboutUs.storyboard | 2 +- .../Sections/Wallet/WalletDetails/WalletManagement.storyboard | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Neuron/Sections/Settings/AboutUs.storyboard b/Neuron/Sections/Settings/AboutUs.storyboard index 3b0069e2..da3ee0d3 100644 --- a/Neuron/Sections/Settings/AboutUs.storyboard +++ b/Neuron/Sections/Settings/AboutUs.storyboard @@ -20,7 +20,7 @@ - + diff --git a/Neuron/Sections/Wallet/WalletDetails/WalletManagement.storyboard b/Neuron/Sections/Wallet/WalletDetails/WalletManagement.storyboard index 49a853b7..865d0c22 100644 --- a/Neuron/Sections/Wallet/WalletDetails/WalletManagement.storyboard +++ b/Neuron/Sections/Wallet/WalletDetails/WalletManagement.storyboard @@ -44,7 +44,7 @@ - + @@ -88,7 +88,7 @@ - + From b7a7f7a1f0c56da5eab5582cef71f0b63474366a Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 22 Nov 2018 15:40:46 +0900 Subject: [PATCH 165/315] Set DApp view title --- Neuron/Base.lproj/Main.storyboard | 4 ++-- Neuron/Sections/Dapp/WebView/DappViewController.swift | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Neuron/Base.lproj/Main.storyboard b/Neuron/Base.lproj/Main.storyboard index 33abb69c..111b0a1a 100644 --- a/Neuron/Base.lproj/Main.storyboard +++ b/Neuron/Base.lproj/Main.storyboard @@ -74,7 +74,7 @@ - + @@ -87,7 +87,7 @@ - + diff --git a/Neuron/Sections/Dapp/WebView/DappViewController.swift b/Neuron/Sections/Dapp/WebView/DappViewController.swift index e7ab8aa4..0acc2529 100644 --- a/Neuron/Sections/Dapp/WebView/DappViewController.swift +++ b/Neuron/Sections/Dapp/WebView/DappViewController.swift @@ -23,6 +23,8 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, override func viewDidLoad() { super.viewDidLoad() + self.title = "应用" + addWebView() layoutWebView() From fab48b5763795ad367d57bb38c2e0e3e7a7f69ec Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 22 Nov 2018 14:47:46 +0800 Subject: [PATCH 166/315] MBA token cannot query transaction history --- .../TransactionHistoryPresenter.swift | 28 +++++++++---------- .../TransactionHistoryViewController.swift | 14 +++++----- .../Wallet/Home/WalletViewController.swift | 4 +-- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift b/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift index 85ad2ed1..66ae2354 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift @@ -15,17 +15,14 @@ protocol TransactionHistoryPresenterDelegate: NSObjectProtocol { class TransactionHistoryPresenter: NSObject, TransactionStatusManagerDelegate { private(set) var transactions = [TransactionDetails]() - let token: TokenModel + let token: Token typealias CallbackBlock = ([Int], Error?) -> Void weak var delegate: TransactionHistoryPresenterDelegate? private var hasMoreData = true private var sentTransactions = [TransactionDetails]() - init(token: TokenModel) { + init(token: Token) { self.token = token - walletAddress = WalletRealmTool.getCurrentAppModel().currentWallet!.address - tokenAddress = token.address - tokenType = token.type super.init() TransactionStatusManager.manager.addDelegate(delegate: self) } @@ -52,7 +49,7 @@ class TransactionHistoryPresenter: NSObject, TransactionStatusManagerDelegate { self.loading = false if self.page == 1 { self.transactions = [] - self.sentTransactions = TransactionStatusManager.manager.getTransactions(walletAddress: self.walletAddress, tokenType: self.tokenType, tokenAddress: self.tokenAddress) + self.sentTransactions = TransactionStatusManager.manager.getTransactions(walletAddress: self.token.walletAddress, tokenType: self.token.type, tokenAddress: self.token.address) } self.page += 1 @@ -110,26 +107,27 @@ class TransactionHistoryPresenter: NSObject, TransactionStatusManagerDelegate { private var loading = false private var page: UInt = 1 private var pageSize: UInt = 10 - private let walletAddress: String - private let tokenAddress: String - private let tokenType: TokenType private func loadData() throws -> [TransactionDetails] { - switch tokenType { + switch token.type { case .appChain: - return try AppChainNetwork().getTransactionHistory(walletAddress: walletAddress, page: page, pageSize: pageSize) + if token.symbol == "NATT" { + return try AppChainNetwork().getTransactionHistory(walletAddress: token.walletAddress, page: page, pageSize: pageSize) + } else { + return [] + } case .ether: - return try EthereumNetwork().getTransactionHistory(walletAddress: walletAddress, page: page, pageSize: pageSize) + return try EthereumNetwork().getTransactionHistory(walletAddress: token.walletAddress, page: page, pageSize: pageSize) case .erc20: - return try EthereumNetwork().getErc20TransactionHistory(walletAddress: walletAddress, tokenAddress: tokenAddress, page: page, pageSize: pageSize) + return try EthereumNetwork().getErc20TransactionHistory(walletAddress: token.walletAddress, tokenAddress: token.address, page: page, pageSize: pageSize) case .appChainErc20: - return try AppChainNetwork().getErc20TransactionHistory(walletAddress: walletAddress, tokenAddress: tokenAddress, page: page, pageSize: pageSize) + return try AppChainNetwork().getErc20TransactionHistory(walletAddress: token.walletAddress, tokenAddress: token.address, page: page, pageSize: pageSize) } } // MARK: - TransactionStatusManagerDelegate func sentTransactionInserted(transaction: TransactionDetails) { - guard transaction.from == walletAddress else { + guard transaction.from == token.walletAddress else { return } DispatchQueue.main.async { diff --git a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift index a4d653ff..643b49ac 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift @@ -24,14 +24,14 @@ class TransactionHistoryViewController: UIViewController, UITableViewDelegate, U var presenter: TransactionHistoryPresenter? var tokenProfile: TokenProfile? var tokenType: TokenType = .erc20 - var tokenModel: TokenModel! { + var token: Token! { didSet { - guard tokenModel != nil else { return } - presenter = TransactionHistoryPresenter(token: tokenModel) + guard token != nil else { return } + presenter = TransactionHistoryPresenter(token: token) presenter?.delegate = self Toast.showHUD() _ = view // load view - tokenModel.getProfile { (tokenProfile) in + token.tokenModel.getProfile { (tokenProfile) in self.setupTokenProfile(tokenProfile) self.presenter?.reloadData() } @@ -49,8 +49,8 @@ class TransactionHistoryViewController: UIViewController, UITableViewDelegate, U } setupTokenProfile(nil) tokenProfleView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(clickTokenProfile))) - if tokenModel.symbol == "MBA" || - tokenModel.symbol == "NATT" { + if token.symbol == "MBA" || + token.symbol == "NATT" { warningView.isHidden = false warningHeight.constant = 30.0 } else { @@ -62,7 +62,7 @@ class TransactionHistoryViewController: UIViewController, UITableViewDelegate, U override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "sendTransaction" { let controller = segue.destination as! SendTransactionViewController - controller.token = presenter?.token + controller.token = presenter?.token.tokenModel } } diff --git a/Neuron/Sections/Wallet/Home/WalletViewController.swift b/Neuron/Sections/Wallet/Home/WalletViewController.swift index 4d6f0f22..a9174819 100644 --- a/Neuron/Sections/Wallet/Home/WalletViewController.swift +++ b/Neuron/Sections/Wallet/Home/WalletViewController.swift @@ -51,7 +51,7 @@ class WalletViewController: UIViewController { override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "transactionHistory" { let controller = segue.destination as! TransactionHistoryViewController - controller.tokenModel = sender as? TokenModel + controller.token = sender as? Token } else if segue.identifier == "transaction" { let controller = segue.destination as! SendTransactionViewController controller.enableSwitchToken = true @@ -131,7 +131,7 @@ extension WalletViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) - performSegue(withIdentifier: "transactionHistory", sender: presenter.tokens[indexPath.row].tokenModel) + performSegue(withIdentifier: "transactionHistory", sender: presenter.tokens[indexPath.row]) } func scrollViewDidScroll(_ scrollView: UIScrollView) { From b2b14923cc3f7d01f7ec03912bdddb363766c9b5 Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 22 Nov 2018 16:33:02 +0900 Subject: [PATCH 167/315] Add theme color asset sets --- .../Theme Colors/Contents.json | 6 ++++++ .../Theme Colors/Neutral/Contents.json | 6 ++++++ .../dark_bg_color.colorset/Contents.json | 20 +++++++++++++++++++ .../headline_color.colorset/Contents.json | 20 +++++++++++++++++++ .../Neutral/info_color.colorset/Contents.json | 20 +++++++++++++++++++ .../subhead_color.colorset/Contents.json | 20 +++++++++++++++++++ .../Theme Colors/Primary/Contents.json | 6 ++++++ .../branding_color.colorset/Contents.json | 20 +++++++++++++++++++ .../label_bg_color.colorset/Contents.json | 20 +++++++++++++++++++ .../label_border_color.colorset/Contents.json | 20 +++++++++++++++++++ .../Theme Colors/Secondary/Contents.json | 6 ++++++ .../secondary_color.colorset/Contents.json | 20 +++++++++++++++++++ .../warning_bg_color.colorset/Contents.json | 20 +++++++++++++++++++ .../warning_color.colorset/Contents.json | 20 +++++++++++++++++++ .../Theme Colors/Weak/Contents.json | 6 ++++++ .../Weak/weak_1_color.colorset/Contents.json | 20 +++++++++++++++++++ .../Weak/weak_2_color.colorset/Contents.json | 20 +++++++++++++++++++ .../Weak/weak_3_color.colorset/Contents.json | 20 +++++++++++++++++++ .../Weak/weak_4_color.colorset/Contents.json | 20 +++++++++++++++++++ 19 files changed, 310 insertions(+) create mode 100644 Neuron/Assets.xcassets/Theme Colors/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Neutral/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Neutral/dark_bg_color.colorset/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Neutral/headline_color.colorset/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Neutral/info_color.colorset/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Neutral/subhead_color.colorset/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Primary/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Primary/branding_color.colorset/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Primary/label_bg_color.colorset/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Primary/label_border_color.colorset/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Secondary/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Secondary/secondary_color.colorset/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Secondary/warning_bg_color.colorset/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Secondary/warning_color.colorset/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Weak/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Weak/weak_1_color.colorset/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Weak/weak_2_color.colorset/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Weak/weak_3_color.colorset/Contents.json create mode 100644 Neuron/Assets.xcassets/Theme Colors/Weak/weak_4_color.colorset/Contents.json diff --git a/Neuron/Assets.xcassets/Theme Colors/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Neutral/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Neutral/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Neutral/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Neutral/dark_bg_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Neutral/dark_bg_color.colorset/Contents.json new file mode 100644 index 00000000..95d65532 --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Neutral/dark_bg_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.141", + "alpha" : "1.000", + "blue" : "0.290", + "green" : "0.173" + } + } + } + ] +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Neutral/headline_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Neutral/headline_color.colorset/Contents.json new file mode 100644 index 00000000..d62742f4 --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Neutral/headline_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.180", + "alpha" : "1.000", + "blue" : "0.243", + "green" : "0.192" + } + } + } + ] +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Neutral/info_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Neutral/info_color.colorset/Contents.json new file mode 100644 index 00000000..b23129dc --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Neutral/info_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.569", + "alpha" : "1.000", + "blue" : "0.651", + "green" : "0.588" + } + } + } + ] +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Neutral/subhead_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Neutral/subhead_color.colorset/Contents.json new file mode 100644 index 00000000..4ca8de19 --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Neutral/subhead_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.424", + "alpha" : "1.000", + "blue" : "0.518", + "green" : "0.443" + } + } + } + ] +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Primary/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Primary/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Primary/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Primary/branding_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Primary/branding_color.colorset/Contents.json new file mode 100644 index 00000000..7a09ccb0 --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Primary/branding_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.271", + "alpha" : "1.000", + "blue" : "1.000", + "green" : "0.424" + } + } + } + ] +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Primary/label_bg_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Primary/label_bg_color.colorset/Contents.json new file mode 100644 index 00000000..c396ebaf --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Primary/label_bg_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.271", + "alpha" : "0.100", + "blue" : "1.000", + "green" : "0.424" + } + } + } + ] +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Primary/label_border_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Primary/label_border_color.colorset/Contents.json new file mode 100644 index 00000000..44582095 --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Primary/label_border_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.271", + "alpha" : "0.150", + "blue" : "1.000", + "green" : "0.424" + } + } + } + ] +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Secondary/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Secondary/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Secondary/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Secondary/secondary_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Secondary/secondary_color.colorset/Contents.json new file mode 100644 index 00000000..ce4b6096 --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Secondary/secondary_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.012", + "alpha" : "1.000", + "blue" : "0.545", + "green" : "0.773" + } + } + } + ] +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Secondary/warning_bg_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Secondary/warning_bg_color.colorset/Contents.json new file mode 100644 index 00000000..be7e85ce --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Secondary/warning_bg_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.992", + "alpha" : "1.000", + "blue" : "0.898", + "green" : "0.984" + } + } + } + ] +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Secondary/warning_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Secondary/warning_color.colorset/Contents.json new file mode 100644 index 00000000..0cebe1fa --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Secondary/warning_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.792", + "alpha" : "1.000", + "blue" : "0.035", + "green" : "0.416" + } + } + } + ] +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Weak/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Weak/Contents.json new file mode 100644 index 00000000..da4a164c --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Weak/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Weak/weak_1_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Weak/weak_1_color.colorset/Contents.json new file mode 100644 index 00000000..ab5a3387 --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Weak/weak_1_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0xDB", + "alpha" : "1.000", + "blue" : "0xE3", + "green" : "0xDC" + } + } + } + ] +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Weak/weak_2_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Weak/weak_2_color.colorset/Contents.json new file mode 100644 index 00000000..91110144 --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Weak/weak_2_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0xE9", + "alpha" : "1.000", + "blue" : "0xF0", + "green" : "0xEB" + } + } + } + ] +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Weak/weak_3_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Weak/weak_3_color.colorset/Contents.json new file mode 100644 index 00000000..ef58b4b2 --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Weak/weak_3_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0xF7", + "alpha" : "1.000", + "blue" : "0xF7", + "green" : "0xF7" + } + } + } + ] +} \ No newline at end of file diff --git a/Neuron/Assets.xcassets/Theme Colors/Weak/weak_4_color.colorset/Contents.json b/Neuron/Assets.xcassets/Theme Colors/Weak/weak_4_color.colorset/Contents.json new file mode 100644 index 00000000..15bc11d6 --- /dev/null +++ b/Neuron/Assets.xcassets/Theme Colors/Weak/weak_4_color.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + }, + "colors" : [ + { + "idiom" : "universal", + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0xF8", + "alpha" : "1.000", + "blue" : "0xFA", + "green" : "0xF8" + } + } + } + ] +} \ No newline at end of file From 2ec73ef81e3b56f02f6725a7283af2ccbf73253f Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 22 Nov 2018 16:21:58 +0800 Subject: [PATCH 168/315] Fix wallet layout issue --- .../Wallet/Home/WalletViewController.swift | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Neuron/Sections/Wallet/Home/WalletViewController.swift b/Neuron/Sections/Wallet/Home/WalletViewController.swift index a9174819..593a3e68 100644 --- a/Neuron/Sections/Wallet/Home/WalletViewController.swift +++ b/Neuron/Sections/Wallet/Home/WalletViewController.swift @@ -22,6 +22,12 @@ class WalletViewController: UIViewController { private var walletCountObserve: NotificationToken? override func viewDidLoad() { super.viewDidLoad() + if #available(iOS 11.0, *) { + tableView.contentInsetAdjustmentBehavior = .never + } else { + automaticallyAdjustsScrollViewInsets = false + } + let refresh = UIRefreshControl() refresh.addTarget(self, action: #selector(WalletViewController.refresh), for: .valueChanged) tableView.refreshControl = refresh @@ -125,6 +131,10 @@ extension WalletViewController: UITableViewDataSource, UITableViewDelegate { return cell } + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 46 + } + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { return tableHeadView } @@ -139,8 +149,8 @@ extension WalletViewController: UITableViewDataSource, UITableViewDelegate { } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - self.refreshButton.alpha = 1.0 - self.tableView.refreshControl?.endRefreshing() + refreshButton.alpha = 1.0 + tableView.refreshControl?.endRefreshing() } } From c077bc68b788799969aac790001371c037da0496 Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 22 Nov 2018 17:28:23 +0900 Subject: [PATCH 169/315] Update AppChain tx sending for CITA version 1 --- .../Services/AppChain/AppChainTxSender.swift | 24 ++++++++++++------- Neuron/Services/Common/ServiceErrors.swift | 1 + 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Neuron/Services/AppChain/AppChainTxSender.swift b/Neuron/Services/AppChain/AppChainTxSender.swift index 751b0a00..c484c31c 100644 --- a/Neuron/Services/AppChain/AppChainTxSender.swift +++ b/Neuron/Services/AppChain/AppChainTxSender.swift @@ -36,26 +36,32 @@ class AppChainTxSender { throw SendTransactionError.invalidDestinationAddress } - let nonce = UUID().uuidString - let appChain = AppChainNetwork.appChain() + let appChain = AppChainNetwork.appChain() // TODO: get rpc node for current AppChain + guard let meta = try? appChain.rpc.getMetaData() else { + throw SendTransactionError.createTransactionIssue + } guard let blockNumber = try? appChain.rpc.blockNumber() else { throw SendTransactionError.createTransactionIssue } + if chainId.description != meta.chainId { + throw SendTransactionError.invalidChainId + } + let transaction = Transaction( to: destinationEthAddress, - nonce: nonce, + nonce: UUID().uuidString, quota: quota, validUntilBlock: blockNumber + UInt64(88), data: data, value: value, - chainId: chainId.description, - version: UInt32(0) + chainId: meta.chainId, + version: meta.version ) let signed = try sign(transaction: transaction, password: password) - let txHash = try appChain.rpc.sendRawTransaction(signedTx: signed) - let sentTransaction = SentTransaction(tokenType: .appChain, from: from.address, hash: txHash, transaction: transaction) - TransactionStatusManager.manager.insertTransaction(transaction: sentTransaction) - return txHash + let txHash = try appChain.rpc.sendRawTransaction(signedTx: signed) + let sentTransaction = SentTransaction(tokenType: .appChain, from: from.address, hash: txHash, transaction: transaction) + TransactionStatusManager.manager.insertTransaction(transaction: sentTransaction) + return txHash } func sendToken(transaction: Transaction, password: String) throws -> TxHash { diff --git a/Neuron/Services/Common/ServiceErrors.swift b/Neuron/Services/Common/ServiceErrors.swift index fabbc8b6..462124fe 100644 --- a/Neuron/Services/Common/ServiceErrors.swift +++ b/Neuron/Services/Common/ServiceErrors.swift @@ -43,6 +43,7 @@ enum SendTransactionError: Error { case createTransactionIssue case invalidPassword case invalidAppChainNode + case invalidChainId case signTXFailed } From dbc44940e4e86ed926eac0117246be52b9b176b0 Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 22 Nov 2018 16:31:31 +0800 Subject: [PATCH 170/315] Update wallet button `tintColor` --- Neuron/Sections/Wallet/Home/Wallet.storyboard | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Neuron/Sections/Wallet/Home/Wallet.storyboard b/Neuron/Sections/Wallet/Home/Wallet.storyboard index 89e37b2e..261518fe 100644 --- a/Neuron/Sections/Wallet/Home/Wallet.storyboard +++ b/Neuron/Sections/Wallet/Home/Wallet.storyboard @@ -128,13 +128,13 @@ - - + diff --git a/Neuron/Sections/Wallet/SwitchWallet/SwitchWallet.storyboard b/Neuron/Sections/Wallet/SwitchWallet/SwitchWallet.storyboard index cd0cfb08..50b55a34 100644 --- a/Neuron/Sections/Wallet/SwitchWallet/SwitchWallet.storyboard +++ b/Neuron/Sections/Wallet/SwitchWallet/SwitchWallet.storyboard @@ -156,7 +156,7 @@ - + From dc59e9be33f1ba51249eea246c5dad204ba9785f Mon Sep 17 00:00:00 2001 From: James Chen Date: Fri, 23 Nov 2018 15:02:59 +0900 Subject: [PATCH 198/315] Fix AddWallet layout warnings --- .../Wallet/AddWallet/AddWallet.storyboard | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Neuron/Sections/Wallet/AddWallet/AddWallet.storyboard b/Neuron/Sections/Wallet/AddWallet/AddWallet.storyboard index 0802e92d..92b8b2d7 100644 --- a/Neuron/Sections/Wallet/AddWallet/AddWallet.storyboard +++ b/Neuron/Sections/Wallet/AddWallet/AddWallet.storyboard @@ -801,19 +801,19 @@ - + - + - + - + - + - - + @@ -874,6 +873,7 @@ + From 69a531a6aa805325ef9135de943bc0871276c15d Mon Sep 17 00:00:00 2001 From: cezres Date: Fri, 23 Nov 2018 14:57:47 +0800 Subject: [PATCH 199/315] Delete check for switch wallet --- Neuron/Sections/Wallet/Home/WalletPresenter.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Neuron/Sections/Wallet/Home/WalletPresenter.swift b/Neuron/Sections/Wallet/Home/WalletPresenter.swift index c54c1634..d4d5b9f9 100644 --- a/Neuron/Sections/Wallet/Home/WalletPresenter.swift +++ b/Neuron/Sections/Wallet/Home/WalletPresenter.swift @@ -26,7 +26,6 @@ protocol WalletPresenterDelegate: NSObjectProtocol { class WalletPresenter { private (set) var currentWallet: WalletModel? { didSet { - guard oldValue?.address != currentWallet?.address else { return } delegate?.walletPresenter(presenter: self, didSwitchWallet: currentWallet!) } } @@ -89,9 +88,7 @@ extension WalletPresenter { appModelObserver = AppModel.current.observe { [weak self] (change) in switch change { case .change(let propertys): - guard let wallet = propertys.first(where: { $0.name == "currentWallet" })?.newValue as? WalletModel else { return } - // TODO: When current wallet gets deleted accessing it would throw a realm invalid object exception - guard wallet.address != self?.currentWallet?.address else { return } + guard propertys.contains(where: { $0.name == "currentWallet" }) else { return } self?.refresh() default: break From 42a99410dde08001c2fa7ba716471b1e1a4dfbe3 Mon Sep 17 00:00:00 2001 From: cezres Date: Fri, 23 Nov 2018 15:23:08 +0800 Subject: [PATCH 200/315] Delete old code --- Neuron/Base.lproj/Main.storyboard | 24 +++++++++--------------- Neuron/MainViewController.swift | 16 ---------------- 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/Neuron/Base.lproj/Main.storyboard b/Neuron/Base.lproj/Main.storyboard index 111b0a1a..1cf01cc5 100644 --- a/Neuron/Base.lproj/Main.storyboard +++ b/Neuron/Base.lproj/Main.storyboard @@ -29,21 +29,15 @@ - - + + - - - - - - - - - - + + + + - + @@ -67,12 +61,12 @@ - + - + diff --git a/Neuron/MainViewController.swift b/Neuron/MainViewController.swift index 793d4a57..6f75a873 100644 --- a/Neuron/MainViewController.swift +++ b/Neuron/MainViewController.swift @@ -16,11 +16,6 @@ class MainViewController: UITabBarController, UITabBarControllerDelegate { delegate = self applyStyle() - - determineWalletViewController() - NotificationCenter.default.addObserver(self, selector: #selector(determineWalletViewController), name: .allWalletsDeleted, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(determineWalletViewController), name: .firstWalletCreated, object: nil) - addNativeTokenMsgToRealm() } @@ -84,15 +79,4 @@ class MainViewController: UITabBarController, UITabBarControllerDelegate { UINavigationBar.fixSpace UINavigationItem.fixSpace } - - @objc - private func determineWalletViewController() { - let walletViewController: UIViewController - if WalletRealmTool.hasWallet() { - walletViewController = UIStoryboard(name: "Wallet", bundle: nil).instantiateInitialViewController()! - } else { - walletViewController = UIStoryboard(name: "AddWallet", bundle: nil).instantiateViewController(withIdentifier: "AddWallet") - } - (viewControllers![1] as! BaseNavigationController).viewControllers = [walletViewController] - } } From dd1878d5350d977f6bf3968a2a85ea458e39031e Mon Sep 17 00:00:00 2001 From: LuFP Date: Fri, 23 Nov 2018 15:53:40 +0800 Subject: [PATCH 201/315] Add DApp collection --- Neuron.xcodeproj/project.pbxproj | 30 ++++++-------- .../Icons/peckshield.imageset/Contents.json | 1 - .../Icons/peckshield.imageset/Group@1x.png | Bin 2748 -> 0 bytes Neuron/Models/{ => Realm}/DAppModel.swift | 0 Neuron/Sections/Dapp/Handler/DAppAction.swift | 2 +- .../Dapp/WebView/BrowserViewController.swift | 4 +- .../WebView/CollectionViewController.swift | 37 +++++++++++++----- .../Dapp/WebView/DAppBrowser.storyboard | 13 ++++-- .../Dapp/WebView/DappViewController.swift | 9 +++-- Neuron/Utils/RealmHelper.swift | 2 +- 10 files changed, 58 insertions(+), 40 deletions(-) delete mode 100644 Neuron/Assets.xcassets/Icons/peckshield.imageset/Group@1x.png rename Neuron/Models/{ => Realm}/DAppModel.swift (100%) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 4abc5e6c..c1a22d45 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -89,8 +89,6 @@ 6EABFB1421995AB800305ED5 /* DAppGyroscopeMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EABFB1321995AB800305ED5 /* DAppGyroscopeMessageHandler.swift */; }; 6EF8F40C2175CAD9004B7587 /* UIControl+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF8F40B2175CAD9004B7587 /* UIControl+Extension.swift */; }; 6EF8F4102176029E004B7587 /* OpenAuthViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF8F40F2176029E004B7587 /* OpenAuthViewController.swift */; }; - 8905660720D0AC120041D4B4 /* AppModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8905660620D0AC120041D4B4 /* AppModel.swift */; }; - 8913437120C78F1000A17AEF /* WalletModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8913437020C78F1000A17AEF /* WalletModel.swift */; }; 8913437520C7D3A400A17AEF /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8913437420C7D3A400A17AEF /* Toast.swift */; }; 8913437820CA56CA00A17AEF /* RealmHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8913437720CA56CA00A17AEF /* RealmHelper.swift */; }; 891B663D213EAA1900B0FCB0 /* WalletManagement.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 891B663C213EAA1900B0FCB0 /* WalletManagement.storyboard */; }; @@ -112,6 +110,9 @@ 8935BA5B216EFCF000C37263 /* init.js in Resources */ = {isa = PBXBuildFile; fileRef = 8935BA5A216EFCF000C37263 /* init.js */; }; 8935BA5D216F48A800C37263 /* Method.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8935BA5C216F48A800C37263 /* Method.swift */; }; 8935BA5F216F631A00C37263 /* ScriptMessageProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8935BA5E216F631A00C37263 /* ScriptMessageProxy.swift */; }; + 89422FE221A7AB6400DD8744 /* AppModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89422FDF21A7AB6300DD8744 /* AppModel.swift */; }; + 89422FE321A7AB6400DD8744 /* TokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89422FE021A7AB6400DD8744 /* TokenModel.swift */; }; + 89422FE421A7AB6400DD8744 /* WalletModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89422FE121A7AB6400DD8744 /* WalletModel.swift */; }; 8951A84C2170A5CC00043228 /* DAppAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8951A84B2170A5CC00043228 /* DAppAction.swift */; }; 8955580520DCA7F700FC92D8 /* PasswordValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8955580420DCA7F700FC92D8 /* PasswordValidator.swift */; }; 8964A17D20BE7F750086848F /* MessageSignController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A17B20BE7F750086848F /* MessageSignController.swift */; }; @@ -150,7 +151,6 @@ 898A1A2B20B7F0FC00ECB465 /* TradeDetailsController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 898A1A2920B7F0FC00ECB465 /* TradeDetailsController.xib */; }; 898A1A2F20B8044900ECB465 /* TradeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898A1A2D20B8044900ECB465 /* TradeTableViewCell.swift */; }; 898A1A3020B8044900ECB465 /* TradeTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 898A1A2E20B8044900ECB465 /* TradeTableViewCell.xib */; }; - 898CA98320EA0F210059ECA3 /* TokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898CA98220EA0F210059ECA3 /* TokenModel.swift */; }; 898CA98820EA23940059ECA3 /* EthNativeTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898CA98720EA23940059ECA3 /* EthNativeTokenService.swift */; }; 898CA98A20EA2E770059ECA3 /* tokens-eth.json in Resources */ = {isa = PBXBuildFile; fileRef = 898CA98920EA2E760059ECA3 /* tokens-eth.json */; }; 899AD19921100F2B00A8AC09 /* NervosNativeTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 899AD19821100F2B00A8AC09 /* NervosNativeTokenService.swift */; }; @@ -307,8 +307,6 @@ 6EF8F40B2175CAD9004B7587 /* UIControl+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIControl+Extension.swift"; sourceTree = ""; }; 6EF8F40F2176029E004B7587 /* OpenAuthViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAuthViewController.swift; sourceTree = ""; }; 71EE3A6382628C3632FE594F /* Pods-NeuronTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NeuronTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-NeuronTests/Pods-NeuronTests.release.xcconfig"; sourceTree = ""; }; - 8905660620D0AC120041D4B4 /* AppModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppModel.swift; sourceTree = ""; }; - 8913437020C78F1000A17AEF /* WalletModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletModel.swift; sourceTree = ""; }; 8913437420C7D3A400A17AEF /* Toast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = ""; }; 8913437720CA56CA00A17AEF /* RealmHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealmHelper.swift; sourceTree = ""; }; 891B663C213EAA1900B0FCB0 /* WalletManagement.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = WalletManagement.storyboard; sourceTree = ""; }; @@ -331,6 +329,9 @@ 8935BA5A216EFCF000C37263 /* init.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = init.js; sourceTree = ""; }; 8935BA5C216F48A800C37263 /* Method.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Method.swift; sourceTree = ""; }; 8935BA5E216F631A00C37263 /* ScriptMessageProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptMessageProxy.swift; sourceTree = ""; }; + 89422FDF21A7AB6300DD8744 /* AppModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppModel.swift; sourceTree = ""; }; + 89422FE021A7AB6400DD8744 /* TokenModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TokenModel.swift; sourceTree = ""; }; + 89422FE121A7AB6400DD8744 /* WalletModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletModel.swift; sourceTree = ""; }; 8951A84B2170A5CC00043228 /* DAppAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppAction.swift; sourceTree = ""; }; 8955580420DCA7F700FC92D8 /* PasswordValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordValidator.swift; sourceTree = ""; }; 8964A17B20BE7F750086848F /* MessageSignController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSignController.swift; sourceTree = ""; }; @@ -375,7 +376,6 @@ 898A1A2920B7F0FC00ECB465 /* TradeDetailsController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TradeDetailsController.xib; sourceTree = ""; }; 898A1A2D20B8044900ECB465 /* TradeTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TradeTableViewCell.swift; sourceTree = ""; }; 898A1A2E20B8044900ECB465 /* TradeTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TradeTableViewCell.xib; sourceTree = ""; }; - 898CA98220EA0F210059ECA3 /* TokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenModel.swift; sourceTree = ""; }; 898CA98720EA23940059ECA3 /* EthNativeTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthNativeTokenService.swift; sourceTree = ""; }; 898CA98920EA2E760059ECA3 /* tokens-eth.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "tokens-eth.json"; sourceTree = ""; }; 899AD19821100F2B00A8AC09 /* NervosNativeTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NervosNativeTokenService.swift; sourceTree = ""; }; @@ -584,9 +584,10 @@ 1A91E1E321A63561000EFD40 /* Realm */ = { isa = PBXGroup; children = ( - 8905660620D0AC120041D4B4 /* AppModel.swift */, - 8913437020C78F1000A17AEF /* WalletModel.swift */, - 898CA98220EA0F210059ECA3 /* TokenModel.swift */, + 89D9418B21A6A88800F1B853 /* DAppModel.swift */, + 89422FDF21A7AB6300DD8744 /* AppModel.swift */, + 89422FE021A7AB6400DD8744 /* TokenModel.swift */, + 89422FE121A7AB6400DD8744 /* WalletModel.swift */, ); path = Realm; sourceTree = ""; @@ -698,11 +699,6 @@ 8913436F20C78D9600A17AEF /* Models */ = { isa = PBXGroup; children = ( - 89B296EA20F4CB6B008558A7 /* TransactionModel.swift */, - 8913437020C78F1000A17AEF /* WalletModel.swift */, - 8905660620D0AC120041D4B4 /* AppModel.swift */, - 898CA98220EA0F210059ECA3 /* TokenModel.swift */, - 89D9418B21A6A88800F1B853 /* DAppModel.swift */, 1A91E1E321A63561000EFD40 /* Realm */, 6E9B948021A408B400D67357 /* Token.swift */, 6E7D3837219C173D0031177B /* TransactionDetails.swift */, @@ -1416,7 +1412,6 @@ 89D84CE72147C671006B0287 /* TraitsCollectionViewCell.swift in Sources */, 6E8168F921887A31007641BA /* TransactionGasPriceViewController.swift in Sources */, 6E83CB8D216F573100029324 /* ProductAgreementViewController.swift in Sources */, - 8905660720D0AC120041D4B4 /* AppModel.swift in Sources */, 6E3B2B6D219ED9240095257D /* Ethereum+TransactionDetails.swift in Sources */, 6E9F9D56219AA71F009ED8B2 /* UINavigationBar+FixSpace.swift in Sources */, 898A1A0F20B6539800ECB465 /* AddAssetTableViewCell.swift in Sources */, @@ -1427,8 +1422,8 @@ 6E3B2B6B219D938F0095257D /* SentTransaction.swift in Sources */, 1A5515E521898A1000D34791 /* WalletKeystoreManager.swift in Sources */, 6EABFB10219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift in Sources */, + 89422FE221A7AB6400DD8744 /* AppModel.swift in Sources */, 6EF8F40C2175CAD9004B7587 /* UIControl+Extension.swift in Sources */, - 898CA98320EA0F210059ECA3 /* TokenModel.swift in Sources */, 6E8D77F021A5264F00EA7674 /* WalletQRCodeViewController.swift in Sources */, 8989E2D32188440D008A1BDE /* SwitchNetworkTableViewCell.swift in Sources */, 898A1A1720B6BE6E00ECB465 /* ChangePasswordController.swift in Sources */, @@ -1451,6 +1446,7 @@ 89D84CE121475755006B0287 /* NFTService.swift in Sources */, 898A1A2A20B7F0FC00ECB465 /* TradeDetailsController.swift in Sources */, 6E876843218023520032EBCE /* CoinMarketCap.swift in Sources */, + 89422FE421A7AB6400DD8744 /* WalletModel.swift in Sources */, 6E9F9D58219AAF78009ED8B2 /* NSObject+Extension.swift in Sources */, 89A196192175D9C800BD5FA4 /* DAppDataHandle.swift in Sources */, 89D9418721A6576700F1B853 /* CollectionViewController.swift in Sources */, @@ -1461,7 +1457,6 @@ 8928C2A320B41BCA00C3103E /* SelectWalletController.swift in Sources */, 89D4AC2E20D59F270097E02B /* WalletManager.swift in Sources */, 89FE21DA20EDB10900A09302 /* CustomERC20TokenService.swift in Sources */, - 8913437120C78F1000A17AEF /* WalletModel.swift in Sources */, 6E9D7CBE216B386F0044176D /* AuthenticationViewController.swift in Sources */, 6E9D7CC5216B490D0044176D /* AuthDeviceViewController.swift in Sources */, 89F7966D213A6B680064808A /* ERC721TableViewCell.swift in Sources */, @@ -1523,6 +1518,7 @@ 89C6145E2141038300DC3DF4 /* LocalCurrencyService.swift in Sources */, 8928C23520AE8F7C00C3103E /* UIStyle.swift in Sources */, 89CFA12621424CAB00635A54 /* AboutUsTableViewController.swift in Sources */, + 89422FE321A7AB6400DD8744 /* TokenModel.swift in Sources */, 6E3B2B71219EDA230095257D /* AppChain+TransactionDetails.swift in Sources */, 1A69465E21916C3D000093A2 /* AppChainTxSender.swift in Sources */, 8928C26B20AED5C100C3103E /* SearchAppController.swift in Sources */, diff --git a/Neuron/Assets.xcassets/Icons/peckshield.imageset/Contents.json b/Neuron/Assets.xcassets/Icons/peckshield.imageset/Contents.json index 2c09ad6c..89d7362c 100644 --- a/Neuron/Assets.xcassets/Icons/peckshield.imageset/Contents.json +++ b/Neuron/Assets.xcassets/Icons/peckshield.imageset/Contents.json @@ -2,7 +2,6 @@ "images" : [ { "idiom" : "universal", - "filename" : "Group@1x.png", "scale" : "1x" }, { diff --git a/Neuron/Assets.xcassets/Icons/peckshield.imageset/Group@1x.png b/Neuron/Assets.xcassets/Icons/peckshield.imageset/Group@1x.png deleted file mode 100644 index ed75f600b8cde2ac79fa4ba8a897dd0f6f29683a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2748 zcmV;t3PbgYP)Z000001b5ch_0Itp) z=>PxwGzlb8Zzw2mLl7JkZO2q1Np%Z}3j-SghPd$p4%BUAJ9Z~t)M-4zvq>s>C-ai1@%cOd;z z)>F2$4zrlEm~oLS1kF@p+_AmQE!1`7Q`-oeABKa@Yt-KJ2JQJl^QFkUoOm?9U#KCo zriwzLZZ4?x?=7Kc$~FZ`3JY@l{|Q6RoUy%@;A?RAtpo431+B7eV=ZWh2huNPZ8_#o z)wm%)e%*n=TFtu&qD#CrAwEpNyE}I6h>|#6o@}J9D{$( zVpNY}TUtrUuRn>>zhC_5M2)lf_3NKI0NS8xQ&6zV>-F-9A=PN!xNcpCJTrIhTy1>% zj0uL>OIn1#buDbVqA+jKcjczvzc(&q2Db|#HZ#U}W#wBFOzw;vnh;-q#~ruyNFF&# zD+4#kxhOB^V>y@m-yWF~XS8Q$XB#qTEURYJHP@tm(a_MAYPp21I+V72Sx(*d?fX5Q zJ#Et+QcoA@zaX(Z2p$7$^ z^ZE?)`)g8yy4x!HcGlT}XW=xrunclcgEWO`LZVKGS_CC0#L%nv-$K)_P6h-bo)r#q zt`wnVX3m*~^FL;S{hpJsn8NM{HZNVOgQsVHa}gs}Y=(`wQ3uahyf`+QCA}a7>tTYw zAukdNQ*7Jr&@}xqXb}SkFOhUPGH=ZC5o6b$gdD1sg4jL$C2%elS0qNa>gb!}np!o!u7JCaP!URPtZ`?z6k6U4fS(RQ8Kix%-6 zux2+SF`g7^#M!6Hsscrb%XBOiLEQZ;^lgPcsaSVIpCkmxdom$#awcrRGlI3C zM%XeL^vB7jNu<*P1VZ;hvUqVu$m8-(2Sazr z|fvsSOEZTf-%sE3MWrW&64tP>(&cIoF_0Q-Yv{u^kiXv z&H|8f^766|LB-Cf+|E*;0#4 z05l4`eF21Z$7|bm564DqMn(pbQEH`qWYhVxQGs90_0c8bqP?MgFV{Npe^e^;T^O?4RXU?w`fH*=jGR_Xd z{#HiL2bRr0*Ht|iN#vEl!vPJ*s1pEvGC`vm8TCYVVon})~zA>!;E$WwvSmqyTd*PY;Y0iy)a40#s<0>LJR!E@7K z{}9ORe=^|{gb^DBM>j=yc$ZuAV4HuT=!kAhh~Gwy%g@hlfjviDq_soX(aG1Q-iK_v z8_htu%9LhrOky2)Z;0|EggXJm;^d+1j~6>>bGxo56n0ef zGaj6*JsGAY)ko;pqW6w>(usz@QN&^t4u?vdhc0;Qxp&IM{*%N z;8IXlZQ%QY!|4-^L=}RhubROh<-iedEU5~op}?yVPL)nK5$uQ=9%9Tvf@lHo9fuxu zNaL$jjje=?HdK`MK}gE3R5~0228}o0d_R6*2Q4>cr|fpu$H+H)AhX}UJJIL6?PBj) zKikG#ySxSS=R-NbbAHIIJafiKxY+$s9yv?-adB2ecI#0(0^@ZwTt7!f_dgxcz5W?+ zT)Vk>4RgglCY!6?6D`zvtclFt;LAqP`v6t@%zoal7Rr<+HkKt>R&T4W z$6cwSq{ydFo<|4Gdt>Ny66cAAqKJcabuCk7%ow9uy1_Y@Q0b`XAzc?kIWCZA6x%T( z5z*=KuyLFl2n6>yv}HOT1gVqTvh2S~zRP-`Ask-1De(5C8ZpV=^po_hr+C`$4v3_O zs@Bp{f1iUc_5A<&E&~Mc@5=l!FC8qt3{?G>Rs09FgX Int { - return 0 + func getCollectionData() { + let realm = try! Realm() + let result = realm.objects(DAppModel.self) + if result.count == 0 { + showOverlay() + } else { + collections.append(contentsOf: result) + tableView.reloadData() + } } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return 0 + return collections.count } - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "collectionTableViewCell", for: indexPath) - - + let cell = tableView.dequeueReusableCell(withIdentifier: "collectionTableViewCell", for: indexPath) as! CollectionTableViewCell + let model = collections[indexPath.row] + cell.dappIconImageView.sd_setImage(with: URL(string: model.iconUrl ?? ""), placeholderImage: UIImage(named: "eth_logo")) + cell.dappNameLabel.text = model.name + cell.timeLabel.text = model.date return cell } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let model = collections[indexPath.row] + let browserViewController: BrowserViewController = UIStoryboard(name: .dAppBrowser).instantiateViewController() + browserViewController.requestUrlStr = model.entry + self.navigationController?.pushViewController(browserViewController, animated: true) + } } diff --git a/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard b/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard index 8cce9155..369b2991 100644 --- a/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard +++ b/Neuron/Sections/Dapp/WebView/DAppBrowser.storyboard @@ -80,10 +80,15 @@ - + + + + + + @@ -158,11 +163,11 @@ - + - + + + + + + + + + + + + + + + + + + + @@ -213,37 +305,121 @@ - + + + + + + + + - - + + - + + + + + + + + + + + + + + + + + + + + + + - - + + - + + + + + + + + + + + + + + + + @@ -254,6 +430,14 @@ + + + + + + + + @@ -547,7 +731,7 @@ - + @@ -622,6 +806,11 @@ + + + + + @@ -709,5 +898,17 @@ + + + + + + + + + + + + diff --git a/Neuron/Sections/Transaction/TransactionGasCostTableViewController.swift b/Neuron/Sections/Transaction/TransactionGasCostTableViewController.swift new file mode 100644 index 00000000..5c2e776c --- /dev/null +++ b/Neuron/Sections/Transaction/TransactionGasCostTableViewController.swift @@ -0,0 +1,126 @@ +// +// TransactionGasCostTableViewController.swift +// Neuron +// +// Created by 晨风 on 2018/11/23. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit + +class TransactionGasCostTableViewController: UITableViewController { + @IBOutlet weak var gasPriceTextField: UITextField! + @IBOutlet weak var gasLimitTextField: UITextField! + @IBOutlet weak var gasCostLabel: UILabel! + @IBOutlet weak var dataTextView: UITextView! + @IBOutlet weak var dataTextPlaceholderLabel: UILabel! + @IBOutlet weak var confirmButton: UIButton! + + override func viewDidLoad() { + super.viewDidLoad() + + // Uncomment the following line to preserve selection between presentations + // self.clearsSelectionOnViewWillAppear = false + + // Uncomment the following line to display an Edit button in the navigation bar for this view controller. + // self.navigationItem.rightBarButtonItem = self.editButtonItem + } + + @IBAction func confirm() { + } + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + // #warning Incomplete implementation, return the number of sections + return 0 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + // #warning Incomplete implementation, return the number of rows + return 0 + } + + /* + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) + + // Configure the cell... + + return cell + } + */ + + /* + // Override to support conditional editing of the table view. + override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + // Return false if you do not want the specified item to be editable. + return true + } + */ + + /* + // Override to support editing the table view. + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { + if editingStyle == .delete { + // Delete the row from the data source + tableView.deleteRows(at: [indexPath], with: .fade) + } else if editingStyle == .insert { + // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view + } + } + */ + + /* + // Override to support rearranging the table view. + override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) { + + } + */ + + /* + // Override to support conditional rearranging of the table view. + override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { + // Return false if you do not want the item to be re-orderable. + return true + } + */ + + /* + // MARK: - Navigation + + // In a storyboard-based application, you will often want to do a little preparation before navigation + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + // Get the new view controller using segue.destination. + // Pass the selected object to the new view controller. + } + */ + +} + +extension TransactionGasCostTableViewController: UITextFieldDelegate { + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + var character = "0123456789" + if !(textField.text?.contains(".") ?? false) { + character += "." + } + guard CharacterSet(charactersIn: character).isSuperset(of: CharacterSet(charactersIn: string)) else { + return false + } + return true + } +} + +extension TransactionGasCostTableViewController: UITextPasteDelegate { + +} + +extension TransactionGasCostTableViewController: UITextViewDelegate { + func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + let character = "0123456789abcdefABCDEF" + guard CharacterSet(charactersIn: character).isSuperset(of: CharacterSet(charactersIn: text)) else { + return false + } + return true + } +} From 0f540aac593579bd024c4e6899a33faadf429c60 Mon Sep 17 00:00:00 2001 From: cezres Date: Fri, 23 Nov 2018 16:49:58 +0800 Subject: [PATCH 206/315] Revert --- .../Icons/peckshield.imageset/Contents.json | 1 + .../Icons/peckshield.imageset/Group@1x.png | Bin 0 -> 2748 bytes 2 files changed, 1 insertion(+) create mode 100644 Neuron/Assets.xcassets/Icons/peckshield.imageset/Group@1x.png diff --git a/Neuron/Assets.xcassets/Icons/peckshield.imageset/Contents.json b/Neuron/Assets.xcassets/Icons/peckshield.imageset/Contents.json index 89d7362c..2c09ad6c 100644 --- a/Neuron/Assets.xcassets/Icons/peckshield.imageset/Contents.json +++ b/Neuron/Assets.xcassets/Icons/peckshield.imageset/Contents.json @@ -2,6 +2,7 @@ "images" : [ { "idiom" : "universal", + "filename" : "Group@1x.png", "scale" : "1x" }, { diff --git a/Neuron/Assets.xcassets/Icons/peckshield.imageset/Group@1x.png b/Neuron/Assets.xcassets/Icons/peckshield.imageset/Group@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..ed75f600b8cde2ac79fa4ba8a897dd0f6f29683a GIT binary patch literal 2748 zcmV;t3PbgYP)Z000001b5ch_0Itp) z=>PxwGzlb8Zzw2mLl7JkZO2q1Np%Z}3j-SghPd$p4%BUAJ9Z~t)M-4zvq>s>C-ai1@%cOd;z z)>F2$4zrlEm~oLS1kF@p+_AmQE!1`7Q`-oeABKa@Yt-KJ2JQJl^QFkUoOm?9U#KCo zriwzLZZ4?x?=7Kc$~FZ`3JY@l{|Q6RoUy%@;A?RAtpo431+B7eV=ZWh2huNPZ8_#o z)wm%)e%*n=TFtu&qD#CrAwEpNyE}I6h>|#6o@}J9D{$( zVpNY}TUtrUuRn>>zhC_5M2)lf_3NKI0NS8xQ&6zV>-F-9A=PN!xNcpCJTrIhTy1>% zj0uL>OIn1#buDbVqA+jKcjczvzc(&q2Db|#HZ#U}W#wBFOzw;vnh;-q#~ruyNFF&# zD+4#kxhOB^V>y@m-yWF~XS8Q$XB#qTEURYJHP@tm(a_MAYPp21I+V72Sx(*d?fX5Q zJ#Et+QcoA@zaX(Z2p$7$^ z^ZE?)`)g8yy4x!HcGlT}XW=xrunclcgEWO`LZVKGS_CC0#L%nv-$K)_P6h-bo)r#q zt`wnVX3m*~^FL;S{hpJsn8NM{HZNVOgQsVHa}gs}Y=(`wQ3uahyf`+QCA}a7>tTYw zAukdNQ*7Jr&@}xqXb}SkFOhUPGH=ZC5o6b$gdD1sg4jL$C2%elS0qNa>gb!}np!o!u7JCaP!URPtZ`?z6k6U4fS(RQ8Kix%-6 zux2+SF`g7^#M!6Hsscrb%XBOiLEQZ;^lgPcsaSVIpCkmxdom$#awcrRGlI3C zM%XeL^vB7jNu<*P1VZ;hvUqVu$m8-(2Sazr z|fvsSOEZTf-%sE3MWrW&64tP>(&cIoF_0Q-Yv{u^kiXv z&H|8f^766|LB-Cf+|E*;0#4 z05l4`eF21Z$7|bm564DqMn(pbQEH`qWYhVxQGs90_0c8bqP?MgFV{Npe^e^;T^O?4RXU?w`fH*=jGR_Xd z{#HiL2bRr0*Ht|iN#vEl!vPJ*s1pEvGC`vm8TCYVVon})~zA>!;E$WwvSmqyTd*PY;Y0iy)a40#s<0>LJR!E@7K z{}9ORe=^|{gb^DBM>j=yc$ZuAV4HuT=!kAhh~Gwy%g@hlfjviDq_soX(aG1Q-iK_v z8_htu%9LhrOky2)Z;0|EggXJm;^d+1j~6>>bGxo56n0ef zGaj6*JsGAY)ko;pqW6w>(usz@QN&^t4u?vdhc0;Qxp&IM{*%N z;8IXlZQ%QY!|4-^L=}RhubROh<-iedEU5~op}?yVPL)nK5$uQ=9%9Tvf@lHo9fuxu zNaL$jjje=?HdK`MK}gE3R5~0228}o0d_R6*2Q4>cr|fpu$H+H)AhX}UJJIL6?PBj) zKikG#ySxSS=R-NbbAHIIJafiKxY+$s9yv?-adB2ecI#0(0^@ZwTt7!f_dgxcz5W?+ zT)Vk>4RgglCY!6?6D`zvtclFt;LAqP`v6t@%zoal7Rr<+HkKt>R&T4W z$6cwSq{ydFo<|4Gdt>Ny66cAAqKJcabuCk7%ow9uy1_Y@Q0b`XAzc?kIWCZA6x%T( z5z*=KuyLFl2n6>yv}HOT1gVqTvh2S~zRP-`Ask-1De(5C8ZpV=^po_hr+C`$4v3_O zs@Bp{f1iUc_5A<&E&~Mc@5=l!FC8qt3{?G>Rs09FgX Date: Fri, 23 Nov 2018 17:04:56 +0800 Subject: [PATCH 207/315] Fix conflict --- Neuron.xcodeproj/project.pbxproj | 20 ++++ .../xcshareddata/xcschemes/Neuron.xcscheme | 91 +++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 Neuron.xcodeproj/xcshareddata/xcschemes/Neuron.xcscheme diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 5749d750..9768d15b 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -111,6 +111,9 @@ 8935BA5B216EFCF000C37263 /* init.js in Resources */ = {isa = PBXBuildFile; fileRef = 8935BA5A216EFCF000C37263 /* init.js */; }; 8935BA5D216F48A800C37263 /* Method.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8935BA5C216F48A800C37263 /* Method.swift */; }; 8935BA5F216F631A00C37263 /* ScriptMessageProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8935BA5E216F631A00C37263 /* ScriptMessageProxy.swift */; }; + 89422FE621A7F92300DD8744 /* DAppModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89422FE521A7F92300DD8744 /* DAppModel.swift */; }; + 89422FE821A7F97300DD8744 /* MyDAppViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89422FE721A7F97300DD8744 /* MyDAppViewController.swift */; }; + 89422FEB21A7F9CA00DD8744 /* CollectionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89422FEA21A7F9C900DD8744 /* CollectionTableViewCell.swift */; }; 8951A84C2170A5CC00043228 /* DAppAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8951A84B2170A5CC00043228 /* DAppAction.swift */; }; 8955580520DCA7F700FC92D8 /* PasswordValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8955580420DCA7F700FC92D8 /* PasswordValidator.swift */; }; 8964A17D20BE7F750086848F /* MessageSignController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8964A17B20BE7F750086848F /* MessageSignController.swift */; }; @@ -326,6 +329,9 @@ 8935BA5A216EFCF000C37263 /* init.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = init.js; sourceTree = ""; }; 8935BA5C216F48A800C37263 /* Method.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Method.swift; sourceTree = ""; }; 8935BA5E216F631A00C37263 /* ScriptMessageProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScriptMessageProxy.swift; sourceTree = ""; }; + 89422FE521A7F92300DD8744 /* DAppModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DAppModel.swift; sourceTree = ""; }; + 89422FE721A7F97300DD8744 /* MyDAppViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyDAppViewController.swift; sourceTree = ""; }; + 89422FEA21A7F9C900DD8744 /* CollectionTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionTableViewCell.swift; sourceTree = ""; }; 8951A84B2170A5CC00043228 /* DAppAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppAction.swift; sourceTree = ""; }; 8955580420DCA7F700FC92D8 /* PasswordValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordValidator.swift; sourceTree = ""; }; 8964A17B20BE7F750086848F /* MessageSignController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSignController.swift; sourceTree = ""; }; @@ -577,6 +583,7 @@ 1A91E1E321A63561000EFD40 /* Realm */ = { isa = PBXGroup; children = ( + 89422FE521A7F92300DD8744 /* DAppModel.swift */, 8905660620D0AC120041D4B4 /* AppModel.swift */, 8913437020C78F1000A17AEF /* WalletModel.swift */, 898CA98220EA0F210059ECA3 /* TokenModel.swift */, @@ -667,6 +674,7 @@ 8928C24820AE91E600C3103E /* DappViewController.swift */, 8928C26920AED5C100C3103E /* SearchAppController.swift */, 89B895B020F8B0CD00B9468B /* BrowserViewController.swift */, + 89422FE721A7F97300DD8744 /* MyDAppViewController.swift */, 8935BA54216EE65500C37263 /* WKWebViewConfiguration.swift */, 89C25C6B20BD421E00007EC1 /* ContractController.swift */, 8972FD0E218B54A100C27147 /* AdvancedViewController.swift */, @@ -780,6 +788,7 @@ 890E3112219BC56A00561F3D /* WebView */, 890E3113219BC56A00561F3D /* Handler */, 6EABFB0921991AF900305ED5 /* Native */, + 89422FE921A7F9C900DD8744 /* TableViewCell */, ); path = Dapp; sourceTree = ""; @@ -853,6 +862,14 @@ path = CustomView; sourceTree = ""; }; + 89422FE921A7F9C900DD8744 /* TableViewCell */ = { + isa = PBXGroup; + children = ( + 89422FEA21A7F9C900DD8744 /* CollectionTableViewCell.swift */, + ); + path = TableViewCell; + sourceTree = ""; + }; 896D042020AE69C1002CFF6A = { isa = PBXGroup; children = ( @@ -1423,6 +1440,7 @@ 898889C82136526800D04AA8 /* MnemonicViewController.swift in Sources */, 1AE3145121A2F5A300F92B2A /* TransactionParamBuilder.swift in Sources */, 1A5515E7218D36B600D34791 /* WalletManager+Errors.swift in Sources */, + 89422FEB21A7F9CA00DD8744 /* CollectionTableViewCell.swift in Sources */, 6E867E0D216C92A900BD6FE5 /* OverlayPresentable.swift in Sources */, 89D84CE121475755006B0287 /* NFTService.swift in Sources */, 898A1A2A20B7F0FC00ECB465 /* TradeDetailsController.swift in Sources */, @@ -1440,6 +1458,7 @@ 6E9D7CBE216B386F0044176D /* AuthenticationViewController.swift in Sources */, 6E9D7CC5216B490D0044176D /* AuthDeviceViewController.swift in Sources */, 89F7966D213A6B680064808A /* ERC721TableViewCell.swift in Sources */, + 89422FE821A7F97300DD8744 /* MyDAppViewController.swift in Sources */, 8964A19320C115A70086848F /* VerifyMnemonicViewController.swift in Sources */, 898A1A0B20B64F6000ECB465 /* AddAssetController.swift in Sources */, 89F9E73E20DEBDE8009E68D4 /* ExportKeystoreController.swift in Sources */, @@ -1486,6 +1505,7 @@ 89A1961B2175E7F300BD5FA4 /* DAppCommand.swift in Sources */, 89B296ED20F4CBA7008558A7 /* ServerAPI.swift in Sources */, 89AB35CA213D113E00A2BF48 /* WalletTableViewCell.swift in Sources */, + 89422FE621A7F92300DD8744 /* DAppModel.swift in Sources */, 8928C24920AE91E600C3103E /* DappViewController.swift in Sources */, 898CA98820EA23940059ECA3 /* EthNativeTokenService.swift in Sources */, 1A6007A021A29C2C00C7B712 /* SuccessPageItem.swift in Sources */, diff --git a/Neuron.xcodeproj/xcshareddata/xcschemes/Neuron.xcscheme b/Neuron.xcodeproj/xcshareddata/xcschemes/Neuron.xcscheme new file mode 100644 index 00000000..e7d30873 --- /dev/null +++ b/Neuron.xcodeproj/xcshareddata/xcschemes/Neuron.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From af06cb15f5d0b3566e9bec3a8b040a360dd5f0eb Mon Sep 17 00:00:00 2001 From: LuFP Date: Fri, 23 Nov 2018 17:23:46 +0800 Subject: [PATCH 208/315] Remove UI action from realm.write block --- Neuron/Sections/Dapp/Handler/DAppAction.swift | 8 ++++---- Neuron/Sections/Dapp/WebView/BrowserViewController.swift | 8 +++++++- Neuron/Sections/Dapp/WebView/DappViewController.swift | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/Neuron/Sections/Dapp/Handler/DAppAction.swift b/Neuron/Sections/Dapp/Handler/DAppAction.swift index 05ad6f2d..21918316 100644 --- a/Neuron/Sections/Dapp/Handler/DAppAction.swift +++ b/Neuron/Sections/Dapp/Handler/DAppAction.swift @@ -18,7 +18,7 @@ struct DAppAction { case emptyTX } - func collectDApp(manifestLink: String?, dappLink: String, title: String) { + func collectDApp(manifestLink: String?, dappLink: String, title: String, completion: @escaping (Bool) -> Void) { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd" let convertedDate = dateFormatter.string(from: Date()) @@ -31,7 +31,7 @@ struct DAppAction { dappModel.date = convertedDate try! realm.write { realm.add(dappModel, update: true) - Toast.showToast(text: "收藏成功") + completion(true) } } else { Alamofire.request(manifestLink!, method: .get).responseJSON { (response) in @@ -53,10 +53,10 @@ struct DAppAction { } try? realm.write { realm.add(dappModel, update: true) - Toast.showToast(text: "收藏成功") + completion(true) } } catch { - Toast.showToast(text: "收藏失败") + completion(false) } } } diff --git a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift index 8b16a1a5..9e1d6af7 100644 --- a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift @@ -119,7 +119,13 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe let relJs = "document.querySelector('head').querySelector('link[rel=manifest]').href;" self.webView.evaluateJavaScript(relJs) { (manifest, _) in if let dappLink = self.webView.url?.absoluteString, let title = self.webView.title { - DAppAction().collectDApp(manifestLink: manifest as? String, dappLink: dappLink, title: title) + DAppAction().collectDApp(manifestLink: manifest as? String, dappLink: dappLink, title: title, completion: { (result) in + if result { + Toast.showToast(text: "收藏成功") + } else { + Toast.showToast(text: "收藏失败") + } + }) } else { Toast.showToast(text: "收藏失败") } diff --git a/Neuron/Sections/Dapp/WebView/DappViewController.swift b/Neuron/Sections/Dapp/WebView/DappViewController.swift index 3872e17a..d322b4ce 100644 --- a/Neuron/Sections/Dapp/WebView/DappViewController.swift +++ b/Neuron/Sections/Dapp/WebView/DappViewController.swift @@ -102,7 +102,7 @@ extension DappViewController: WKScriptMessageHandler { let searchAppController = UIStoryboard(name: "DAppBrowser", bundle: nil).instantiateViewController(withIdentifier: "searchAppController") self.navigationController?.pushViewController(searchAppController, animated: true) case "pushMyDAppView": - let myDAppViewController = UIStoryboard(name: "DAppBrowser", bundle: nil).instantiateViewController(withIdentifier: "MyDAppViewController") + let myDAppViewController: MyDAppViewController = UIStoryboard(name: .dAppBrowser).instantiateViewController() self.navigationController?.pushViewController(myDAppViewController, animated: true) case "pushCollectionView": Toast.showToast(text: "敬请期待") From ec45e92809021df853e48fc82e9424544be18fa6 Mon Sep 17 00:00:00 2001 From: cezres Date: Fri, 23 Nov 2018 17:44:05 +0800 Subject: [PATCH 209/315] Modify gas setting page --- .../Transaction/SendTransaction.storyboard | 22 +++- .../SendTransactionViewController.swift | 3 + ...ransactionGasCostTableViewController.swift | 103 ++++++------------ 3 files changed, 51 insertions(+), 77 deletions(-) diff --git a/Neuron/Sections/Transaction/SendTransaction.storyboard b/Neuron/Sections/Transaction/SendTransaction.storyboard index edc2d14e..6e0b682e 100644 --- a/Neuron/Sections/Transaction/SendTransaction.storyboard +++ b/Neuron/Sections/Transaction/SendTransaction.storyboard @@ -204,7 +204,7 @@ - + - - + + @@ -325,11 +325,23 @@ + - + + + @@ -731,7 +743,7 @@ - + diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index 2895194a..0d07a1c9 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -127,6 +127,9 @@ class SendTransactionViewController: UITableViewController, TransactonSender { let controller = segue.destination as! TransactionSwitchTokenViewController controller.currentToken = token controller.delegate = self + } else if segue.identifier == String(describing: TransactionGasCostTableViewController.self) { + let controller = segue.destination as! TransactionGasCostTableViewController + controller.param = (paramBuilder, token) } } diff --git a/Neuron/Sections/Transaction/TransactionGasCostTableViewController.swift b/Neuron/Sections/Transaction/TransactionGasCostTableViewController.swift index 5c2e776c..92f172cd 100644 --- a/Neuron/Sections/Transaction/TransactionGasCostTableViewController.swift +++ b/Neuron/Sections/Transaction/TransactionGasCostTableViewController.swift @@ -15,87 +15,38 @@ class TransactionGasCostTableViewController: UITableViewController { @IBOutlet weak var dataTextView: UITextView! @IBOutlet weak var dataTextPlaceholderLabel: UILabel! @IBOutlet weak var confirmButton: UIButton! + var param: (TransactionParamBuilder, TokenModel)! + private var paramBuilder: TransactionParamBuilder! + private var observers = [NSKeyValueObservation]() + private let minGasPrice = 1.0 override func viewDidLoad() { super.viewDidLoad() - - // Uncomment the following line to preserve selection between presentations - // self.clearsSelectionOnViewWillAppear = false - - // Uncomment the following line to display an Edit button in the navigation bar for this view controller. - // self.navigationItem.rightBarButtonItem = self.editButtonItem + paramBuilder = TransactionParamBuilder(token: param.1) + paramBuilder.from = WalletRealmTool.getCurrentAppModel().currentWallet!.address + paramBuilder.to = param.0.to + + gasPriceTextField.text = paramBuilder.fetchedGasPrice.weiToGwei().trailingZerosTrimmed + gasLimitTextField.text = paramBuilder.gasLimit.description + observers.append(paramBuilder.observe(\.txFeeNatural, options: [.initial]) { [weak self](_, _) in + self?.updateGasCost() + }) } @IBAction func confirm() { + let gasPrice = Double(gasPriceTextField.text!)! + if gasPrice < minGasPrice { + Toast.showToast(text: "您的GasPrice设置过低,建议输入推荐值以快速转账") + return + } + param.0.gasPrice = paramBuilder.gasPrice + param.0.gasLimit = paramBuilder.gasLimit + navigationController?.popViewController(animated: true) } - // MARK: - Table view data source - - override func numberOfSections(in tableView: UITableView) -> Int { - // #warning Incomplete implementation, return the number of sections - return 0 - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - // #warning Incomplete implementation, return the number of rows - return 0 - } - - /* - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifier", for: indexPath) - - // Configure the cell... - - return cell - } - */ - - /* - // Override to support conditional editing of the table view. - override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - // Return false if you do not want the specified item to be editable. - return true - } - */ - - /* - // Override to support editing the table view. - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { - if editingStyle == .delete { - // Delete the row from the data source - tableView.deleteRows(at: [indexPath], with: .fade) - } else if editingStyle == .insert { - // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view - } - } - */ - - /* - // Override to support rearranging the table view. - override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) { - - } - */ - - /* - // Override to support conditional rearranging of the table view. - override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { - // Return false if you do not want the item to be re-orderable. - return true - } - */ - - /* - // MARK: - Navigation - - // In a storyboard-based application, you will often want to do a little preparation before navigation - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - // Get the new view controller using segue.destination. - // Pass the selected object to the new view controller. + private func updateGasCost() { + gasCostLabel.text = "\(paramBuilder.txFeeNatural.decimal) \(paramBuilder.nativeCoinSymbol)" } - */ - } extension TransactionGasCostTableViewController: UITextFieldDelegate { @@ -109,6 +60,14 @@ extension TransactionGasCostTableViewController: UITextFieldDelegate { } return true } + + func textFieldDidEndEditing(_ textField: UITextField) { + if textField == gasPriceTextField { + paramBuilder.gasPrice = Double(gasPriceTextField.text!)!.gweiToWei() + } else if textField == gasLimitTextField { + paramBuilder.gasLimit = UInt64(gasLimitTextField.text!) ?? GasCalculator.defaultGasLimit + } + } } extension TransactionGasCostTableViewController: UITextPasteDelegate { From 397c971ebd07625155806fef39d2dde8a68ab4eb Mon Sep 17 00:00:00 2001 From: cezres Date: Fri, 23 Nov 2018 17:46:13 +0800 Subject: [PATCH 210/315] Add test action --- .../xcshareddata/xcschemes/Neuron.xcscheme | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Neuron.xcodeproj/xcshareddata/xcschemes/Neuron.xcscheme b/Neuron.xcodeproj/xcshareddata/xcschemes/Neuron.xcscheme index e7d30873..539e1f40 100644 --- a/Neuron.xcodeproj/xcshareddata/xcschemes/Neuron.xcscheme +++ b/Neuron.xcodeproj/xcshareddata/xcschemes/Neuron.xcscheme @@ -28,6 +28,16 @@ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> + + + + Date: Fri, 23 Nov 2018 17:56:40 +0800 Subject: [PATCH 211/315] DApp transaction can input decimal point --- Neuron/Sections/Dapp/WebView/AdvancedViewController.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Neuron/Sections/Dapp/WebView/AdvancedViewController.swift b/Neuron/Sections/Dapp/WebView/AdvancedViewController.swift index 3cd8bd0b..ed333ea7 100644 --- a/Neuron/Sections/Dapp/WebView/AdvancedViewController.swift +++ b/Neuron/Sections/Dapp/WebView/AdvancedViewController.swift @@ -115,7 +115,13 @@ class AdvancedViewController: UIViewController { extension AdvancedViewController: UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { if textField == gasPriceTextField { - guard CharacterSet(charactersIn: "0123456789").isSuperset(of: CharacterSet(charactersIn: string)) else { + let character: String + if (textField.text?.contains("."))! { + character = "0123456789" + } else { + character = "0123456789." + } + guard CharacterSet(charactersIn: character).isSuperset(of: CharacterSet(charactersIn: string)) else { return false } return true From 665922d54158f10feafc10938729d06ee9dbdfa1 Mon Sep 17 00:00:00 2001 From: LuFP Date: Sat, 24 Nov 2018 10:31:01 +0800 Subject: [PATCH 212/315] Drop FixSwipeBackable and fix WKWebView back forward gesture --- .../xcshareddata/xcschemes/Neuron.xcscheme | 101 ------------------ .../xcshareddata/xcschemes/Neuron.xcscheme | 101 ------------------ .../Dapp/WebView/BrowserViewController.swift | 16 +-- 3 files changed, 10 insertions(+), 208 deletions(-) delete mode 100644 Neuron.xcodeproj/xcshareddata/xcschemes/Neuron.xcscheme delete mode 100644 Neuron.xcworkspace/xcshareddata/xcschemes/Neuron.xcscheme diff --git a/Neuron.xcodeproj/xcshareddata/xcschemes/Neuron.xcscheme b/Neuron.xcodeproj/xcshareddata/xcschemes/Neuron.xcscheme deleted file mode 100644 index 539e1f40..00000000 --- a/Neuron.xcodeproj/xcshareddata/xcschemes/Neuron.xcscheme +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Neuron.xcworkspace/xcshareddata/xcschemes/Neuron.xcscheme b/Neuron.xcworkspace/xcshareddata/xcschemes/Neuron.xcscheme deleted file mode 100644 index 539e1f40..00000000 --- a/Neuron.xcworkspace/xcshareddata/xcschemes/Neuron.xcscheme +++ /dev/null @@ -1,101 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift index 9e1d6af7..5a88a2c4 100644 --- a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift @@ -10,7 +10,7 @@ import UIKit import WebKit import RealmSwift -class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipeBackable { +class BrowserViewController: UIViewController, ErrorOverlayPresentable { @IBOutlet private weak var directionView: UIView! @IBOutlet private weak var backButton: UIButton! @IBOutlet private weak var forwardButton: UIButton! @@ -33,6 +33,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe webView.uiDelegate = self webView.scrollView.delegate = self webView.addAllNativeFunctionHandler() + webView.allowsBackForwardNavigationGestures = true return webView }() @@ -56,7 +57,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe override func viewDidLoad() { super.viewDidLoad() - + navigationController?.interactivePopGestureRecognizer?.delegate = self view.addSubview(webView) layoutWebView() view.addSubview(progressView) @@ -78,9 +79,6 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe self?.webView.load(URLRequest(url: url)) } navigationItem.fixSpace() - // fix swipe back - let gestureRecognizer = fixSwipeBack() - webView.addGestureRecognizer(gestureRecognizer) observations.append(webView.observe(\.estimatedProgress) { [weak self] (webView, _) in guard let self = self else { @@ -93,6 +91,7 @@ class BrowserViewController: UIViewController, ErrorOverlayPresentable, FixSwipe self.progressView.alpha = 0 }, completion: { (_) in self.progressView.setProgress(0.0, animated: false) + self.title = webView.title }) } }) @@ -276,7 +275,6 @@ extension BrowserViewController { extension BrowserViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - title = webView.title updateNavigationButtons() let relJs = "document.querySelector('head').querySelector('link[rel=manifest]').href;" @@ -341,3 +339,9 @@ extension BrowserViewController: ContractControllerDelegate, MessageSignControll } } } + +extension BrowserViewController: UIGestureRecognizerDelegate { + func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + return navigationController!.viewControllers.count > 1 + } +} From c083a1aea969ff976d0516ec79c364ee3301978e Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 26 Nov 2018 10:29:31 +0800 Subject: [PATCH 213/315] Modify gas setting page --- Neuron/MainViewController.swift | 2 +- .../SendTransactionViewController.swift | 2 +- ...ransactionGasCostTableViewController.swift | 19 ++++++++++--------- .../Transaction/TransactionParamBuilder.swift | 14 ++++++++++++++ 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Neuron/MainViewController.swift b/Neuron/MainViewController.swift index 6f75a873..76392895 100644 --- a/Neuron/MainViewController.swift +++ b/Neuron/MainViewController.swift @@ -67,7 +67,7 @@ class MainViewController: UITabBarController, UITabBarControllerDelegate { } private func applyStyle() { - UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor(named: "tint_color")], for: .selected) + UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.foregroundColor: UIColor(named: "tint_color")!], for: .selected) let navigationBarBackImage = UIImage(named: "nav_darkback")!.withRenderingMode(.alwaysOriginal) UINavigationBar.appearance().backIndicatorImage = navigationBarBackImage diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index 0d07a1c9..7acdfb7d 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -129,7 +129,7 @@ class SendTransactionViewController: UITableViewController, TransactonSender { controller.delegate = self } else if segue.identifier == String(describing: TransactionGasCostTableViewController.self) { let controller = segue.destination as! TransactionGasCostTableViewController - controller.param = (paramBuilder, token) + controller.param = paramBuilder } } diff --git a/Neuron/Sections/Transaction/TransactionGasCostTableViewController.swift b/Neuron/Sections/Transaction/TransactionGasCostTableViewController.swift index 92f172cd..14bb524b 100644 --- a/Neuron/Sections/Transaction/TransactionGasCostTableViewController.swift +++ b/Neuron/Sections/Transaction/TransactionGasCostTableViewController.swift @@ -15,19 +15,14 @@ class TransactionGasCostTableViewController: UITableViewController { @IBOutlet weak var dataTextView: UITextView! @IBOutlet weak var dataTextPlaceholderLabel: UILabel! @IBOutlet weak var confirmButton: UIButton! - var param: (TransactionParamBuilder, TokenModel)! + var param: TransactionParamBuilder! private var paramBuilder: TransactionParamBuilder! private var observers = [NSKeyValueObservation]() private let minGasPrice = 1.0 override func viewDidLoad() { super.viewDidLoad() - paramBuilder = TransactionParamBuilder(token: param.1) - paramBuilder.from = WalletRealmTool.getCurrentAppModel().currentWallet!.address - paramBuilder.to = param.0.to - - gasPriceTextField.text = paramBuilder.fetchedGasPrice.weiToGwei().trailingZerosTrimmed - gasLimitTextField.text = paramBuilder.gasLimit.description + paramBuilder = TransactionParamBuilder(builder: param) observers.append(paramBuilder.observe(\.txFeeNatural, options: [.initial]) { [weak self](_, _) in self?.updateGasCost() }) @@ -39,14 +34,20 @@ class TransactionGasCostTableViewController: UITableViewController { Toast.showToast(text: "您的GasPrice设置过低,建议输入推荐值以快速转账") return } - param.0.gasPrice = paramBuilder.gasPrice - param.0.gasLimit = paramBuilder.gasLimit + param.gasPrice = paramBuilder.gasPrice + param.gasLimit = paramBuilder.gasLimit navigationController?.popViewController(animated: true) } private func updateGasCost() { + gasPriceTextField.text = paramBuilder.fetchedGasPrice.weiToGwei().trailingZerosTrimmed + gasLimitTextField.text = paramBuilder.gasLimit.description gasCostLabel.text = "\(paramBuilder.txFeeNatural.decimal) \(paramBuilder.nativeCoinSymbol)" } + + override func tableView(_ tableView: UITableView, shouldHighlightRowAt indexPath: IndexPath) -> Bool { + return false + } } extension TransactionGasCostTableViewController: UITextFieldDelegate { diff --git a/Neuron/Sections/Transaction/TransactionParamBuilder.swift b/Neuron/Sections/Transaction/TransactionParamBuilder.swift index 03038a48..2fffe548 100644 --- a/Neuron/Sections/Transaction/TransactionParamBuilder.swift +++ b/Neuron/Sections/Transaction/TransactionParamBuilder.swift @@ -75,6 +75,20 @@ class TransactionParamBuilder: NSObject { fetchGasLimit() } + init(builder: TransactionParamBuilder) { + tokenType = builder.tokenType + rpcNode = builder.rpcNode + decimals = builder.decimals + chainId = builder.chainId + contractAddress = builder.contractAddress + symbol = builder.symbol + nativeCoinSymbol = builder.nativeCoinSymbol + tokenBalance = builder.tokenBalance + super.init() + gasPrice = builder.gasPrice + gasLimit = builder.gasLimit + } + private func fetchGasPrice() { func fetched(price: BigUInt) { self.fetchedGasPrice = price From b5cc6ee8b5b4ff22382c1f3e97f9d6483bd77d61 Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 26 Nov 2018 14:54:03 +0900 Subject: [PATCH 214/315] WalletManager.Error i18n --- .../WalletManager/WalletManager+Errors.swift | 2 +- Neuron/en.lproj/Localizable.strings | 20 +++++++++---------- Neuron/zh-Hans.lproj/Localizable.strings | 20 +++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Neuron/Services/WalletManager/WalletManager+Errors.swift b/Neuron/Services/WalletManager/WalletManager+Errors.swift index d06675be..ea49285e 100644 --- a/Neuron/Services/WalletManager/WalletManager+Errors.swift +++ b/Neuron/Services/WalletManager/WalletManager+Errors.swift @@ -22,7 +22,7 @@ extension WalletManager { case unknown var errorDescription: String? { - return NSLocalizedString("WalletManager.\(rawValue)", comment: "") + return NSLocalizedString("WalletManager.Error.\(rawValue)", comment: "") } } } diff --git a/Neuron/en.lproj/Localizable.strings b/Neuron/en.lproj/Localizable.strings index 6e6aec06..9ea56a34 100644 --- a/Neuron/en.lproj/Localizable.strings +++ b/Neuron/en.lproj/Localizable.strings @@ -8,13 +8,13 @@ // Wallet Manager Errors -"WalletManager.invalidPassword" = "Invalid password"; -"WalletManager.invalidPrivateKey" = "Invalid Private Key"; -"WalletManager.invalidKeystore" = "Invalid Keystore"; -"WalletManager.invalidMnemonic" = "Invalid Mnemonic"; -"WalletManager.accountAlreadyExists" = "You already have this wallet"; -"WalletManager.accountNotFound" = "Wallet not found"; -"WalletManager.failedToDeleteAccount" = "Failed to delete wallet"; -"WalletManager.failedToUpdatePassword" = "Failed to update wallet password"; -"WalletManager.failedToSaveKeystore" = "Failed to save the keystore"; -"WalletManager.unknown" = "Unknow error"; +"WalletManager.Error.invalidPassword" = "Invalid password"; +"WalletManager.Error.invalidPrivateKey" = "Invalid Private Key"; +"WalletManager.Error.invalidKeystore" = "Invalid Keystore"; +"WalletManager.Error.invalidMnemonic" = "Invalid Mnemonic"; +"WalletManager.Error.accountAlreadyExists" = "You already have this wallet"; +"WalletManager.Error.accountNotFound" = "Wallet not found"; +"WalletManager.Error.failedToDeleteAccount" = "Failed to delete wallet"; +"WalletManager.Error.failedToUpdatePassword" = "Failed to update wallet password"; +"WalletManager.Error.failedToSaveKeystore" = "Failed to save the keystore"; +"WalletManager.Error.unknown" = "Unknow error"; diff --git a/Neuron/zh-Hans.lproj/Localizable.strings b/Neuron/zh-Hans.lproj/Localizable.strings index 8a4c6d04..42e86d95 100644 --- a/Neuron/zh-Hans.lproj/Localizable.strings +++ b/Neuron/zh-Hans.lproj/Localizable.strings @@ -8,13 +8,13 @@ // Wallet Manager Errors -"WalletManager.invalidPassword" = "密码不正确"; -"WalletManager.invalidPrivateKey" = "私钥不正确"; -"WalletManager.invalidKeystore" = "Keystore不正确"; -"WalletManager.invalidMnemonic" = "助记词不正确"; -"WalletManager.accountAlreadyExists" = "该钱包已存在"; -"WalletManager.accountNotFound" = "未找到该钱包"; -"WalletManager.failedToDeleteAccount" = "删除钱包失败"; -"WalletManager.failedToUpdatePassword" = "修改密码失败"; -"WalletManager.failedToSaveKeystore" = "保存keystore失败"; -"WalletManager.unknown" = "未知错误"; +"WalletManager.Error.invalidPassword" = "密码不正确"; +"WalletManager.Error.invalidPrivateKey" = "私钥不正确"; +"WalletManager.Error.invalidKeystore" = "Keystore不正确"; +"WalletManager.Error.invalidMnemonic" = "助记词不正确"; +"WalletManager.Error.accountAlreadyExists" = "该钱包已存在"; +"WalletManager.Error.accountNotFound" = "未找到该钱包"; +"WalletManager.Error.failedToDeleteAccount" = "删除钱包失败"; +"WalletManager.Error.failedToUpdatePassword" = "修改密码失败"; +"WalletManager.Error.failedToSaveKeystore" = "保存keystore失败"; +"WalletManager.Error.unknown" = "未知错误"; From fcf63726bf90acf9c332e8eff73049031ee2ab24 Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 26 Nov 2018 15:36:23 +0900 Subject: [PATCH 215/315] Localize SendTransactionError --- Neuron/Services/Common/ServiceErrors.swift | 14 ++++---------- Neuron/en.lproj/Localizable.strings | 10 ++++++++++ Neuron/zh-Hans.lproj/Localizable.strings | 10 ++++++++++ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Neuron/Services/Common/ServiceErrors.swift b/Neuron/Services/Common/ServiceErrors.swift index 462124fe..6b50ed09 100644 --- a/Neuron/Services/Common/ServiceErrors.swift +++ b/Neuron/Services/Common/ServiceErrors.swift @@ -30,23 +30,17 @@ enum SignMessageResult { case error(Error) } -enum SendTransactionError: Error { +enum SendTransactionError: String, LocalizedError { case invalidSourceAddress case invalidDestinationAddress case invalidContractAddress - case invalidAmountFormat - case contractLoadingError - case retrievingGasPriceError - case retrievingEstimatedGasError - case emptyResult case noAvailableKeys case createTransactionIssue - case invalidPassword case invalidAppChainNode case invalidChainId case signTXFailed -} -enum TransactionError: Error { - case requestfailed + var errorDescription: String? { + return NSLocalizedString("SendTransactionError.\(rawValue)", comment: "") + } } diff --git a/Neuron/en.lproj/Localizable.strings b/Neuron/en.lproj/Localizable.strings index 9ea56a34..67aad6fd 100644 --- a/Neuron/en.lproj/Localizable.strings +++ b/Neuron/en.lproj/Localizable.strings @@ -18,3 +18,13 @@ "WalletManager.Error.failedToUpdatePassword" = "Failed to update wallet password"; "WalletManager.Error.failedToSaveKeystore" = "Failed to save the keystore"; "WalletManager.Error.unknown" = "Unknow error"; + +// Send Transactoin Error + +"SendTransactionError.invalidSourceAddress" = "Invalid source address"; +"SendTransactionError.invalidDestinationAddress" = "Invalid destination address"; +"SendTransactionError.invalidContractAddress" = "Invalid contract ADdress"; +"SendTransactionError.noAvailableKeys" = "Wallet not found"; +"SendTransactionError.createTransactionIssue" = "Failed to create transaction"; +"SendTransactionError.invalidChainId" = "Invalid chain ID"; +"SendTransactionError.signTXFailed" = "Failed to sign the transaction"; diff --git a/Neuron/zh-Hans.lproj/Localizable.strings b/Neuron/zh-Hans.lproj/Localizable.strings index 42e86d95..2592cc8f 100644 --- a/Neuron/zh-Hans.lproj/Localizable.strings +++ b/Neuron/zh-Hans.lproj/Localizable.strings @@ -18,3 +18,13 @@ "WalletManager.Error.failedToUpdatePassword" = "修改密码失败"; "WalletManager.Error.failedToSaveKeystore" = "保存keystore失败"; "WalletManager.Error.unknown" = "未知错误"; + +// Send Transactoin Error + +"SendTransactionError.invalidSourceAddress" = "发送地址不正确"; +"SendTransactionError.invalidDestinationAddress" = "接收地址不正确"; +"SendTransactionError.invalidContractAddress" = "合约地址不正确"; +"SendTransactionError.noAvailableKeys" = "未找到该钱包"; +"SendTransactionError.createTransactionIssue" = "创建交易失败"; +"SendTransactionError.invalidChainId" = "Chain ID不正确"; +"SendTransactionError.signTXFailed" = "交易签名失败"; From ecb130860a71d23f920e850a1b423bcc0b4ad01e Mon Sep 17 00:00:00 2001 From: LuFP Date: Mon, 26 Nov 2018 15:35:45 +0800 Subject: [PATCH 216/315] Feature NFTViewController --- Neuron.xcodeproj/project.pbxproj | 22 +++++++++---------- .../DAppDeviceMotionMessageHandler.swift | 0 .../DAppGyroscopeMessageHandler.swift | 0 .../DAppNativeMessageHandler.swift | 0 .../DAppQRCodeMessageHandler.swift | 0 .../DAppTakePhotoMessageHandler.swift | 0 .../MyDAppViewController.swift | 2 +- .../Home => Dapp/Native}/NFTDetail.storyboard | 0 .../Native}/NFTDetailViewController.swift | 0 .../Native}/NFTFooterReusableView.swift | 0 .../Native}/NFTHeaderReusableView.swift | 0 .../Native}/NFTViewController.swift | 22 +++---------------- .../Dapp/WebView/DappViewController.swift | 3 ++- Neuron/Sections/Wallet/Home/Wallet.storyboard | 8 ++++++- Neuron/Utils/OverlayPresentable.swift | 11 ++++++++++ 15 files changed, 35 insertions(+), 33 deletions(-) rename Neuron/Sections/Dapp/{Native => Handler}/DAppDeviceMotionMessageHandler.swift (100%) rename Neuron/Sections/Dapp/{Native => Handler}/DAppGyroscopeMessageHandler.swift (100%) rename Neuron/Sections/Dapp/{Native => Handler}/DAppNativeMessageHandler.swift (100%) rename Neuron/Sections/Dapp/{Native => Handler}/DAppQRCodeMessageHandler.swift (100%) rename Neuron/Sections/Dapp/{Native => Handler}/DAppTakePhotoMessageHandler.swift (100%) rename Neuron/Sections/Dapp/{WebView => Native}/MyDAppViewController.swift (98%) rename Neuron/Sections/{Wallet/Home => Dapp/Native}/NFTDetail.storyboard (100%) rename Neuron/Sections/{Wallet/Home => Dapp/Native}/NFTDetailViewController.swift (100%) rename Neuron/Sections/{Wallet/Home => Dapp/Native}/NFTFooterReusableView.swift (100%) rename Neuron/Sections/{Wallet/Home => Dapp/Native}/NFTHeaderReusableView.swift (100%) rename Neuron/Sections/{Wallet/Home => Dapp/Native}/NFTViewController.swift (77%) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index b809fa12..1fad582f 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -657,11 +657,12 @@ 6EABFB0921991AF900305ED5 /* Native */ = { isa = PBXGroup; children = ( - 6EABFB0C21991CB300305ED5 /* DAppNativeMessageHandler.swift */, - 6EABFB112199520F00305ED5 /* DAppTakePhotoMessageHandler.swift */, - 6EABFB0A21991BC200305ED5 /* DAppQRCodeMessageHandler.swift */, - 6EABFB0F219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift */, - 6EABFB1321995AB800305ED5 /* DAppGyroscopeMessageHandler.swift */, + 89422FE721A7F97300DD8744 /* MyDAppViewController.swift */, + 1A092244213580C900CAED5D /* NFTViewController.swift */, + 89D84CEA2148C03B006B0287 /* NFTHeaderReusableView.swift */, + 89D84CEC2148C056006B0287 /* NFTFooterReusableView.swift */, + 89D84CE82147D926006B0287 /* NFTDetailViewController.swift */, + 89D84CE2214790B5006B0287 /* NFTDetail.storyboard */, ); path = Native; sourceTree = ""; @@ -673,7 +674,6 @@ 8928C24820AE91E600C3103E /* DappViewController.swift */, 8928C26920AED5C100C3103E /* SearchAppController.swift */, 89B895B020F8B0CD00B9468B /* BrowserViewController.swift */, - 89422FE721A7F97300DD8744 /* MyDAppViewController.swift */, 8935BA54216EE65500C37263 /* WKWebViewConfiguration.swift */, 89C25C6B20BD421E00007EC1 /* ContractController.swift */, 8972FD0E218B54A100C27147 /* AdvancedViewController.swift */, @@ -690,6 +690,11 @@ 8951A84B2170A5CC00043228 /* DAppAction.swift */, 89A196182175D9C800BD5FA4 /* DAppDataHandle.swift */, 89A1961A2175E7F300BD5FA4 /* DAppCommand.swift */, + 6EABFB0C21991CB300305ED5 /* DAppNativeMessageHandler.swift */, + 6EABFB112199520F00305ED5 /* DAppTakePhotoMessageHandler.swift */, + 6EABFB0A21991BC200305ED5 /* DAppQRCodeMessageHandler.swift */, + 6EABFB0F219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift */, + 6EABFB1321995AB800305ED5 /* DAppGyroscopeMessageHandler.swift */, ); path = Handler; sourceTree = ""; @@ -951,11 +956,6 @@ 1A2BFA19213513D60065496B /* Wallet.storyboard */, 8928C27820B2A61900C3103E /* WalletViewController.swift */, 6E9B947E21A4086400D67357 /* WalletPresenter.swift */, - 1A092244213580C900CAED5D /* NFTViewController.swift */, - 89D84CEA2148C03B006B0287 /* NFTHeaderReusableView.swift */, - 89D84CEC2148C056006B0287 /* NFTFooterReusableView.swift */, - 89D84CE82147D926006B0287 /* NFTDetailViewController.swift */, - 89D84CE2214790B5006B0287 /* NFTDetail.storyboard */, 6E8D77EF21A5264F00EA7674 /* WalletQRCodeViewController.swift */, ); path = Home; diff --git a/Neuron/Sections/Dapp/Native/DAppDeviceMotionMessageHandler.swift b/Neuron/Sections/Dapp/Handler/DAppDeviceMotionMessageHandler.swift similarity index 100% rename from Neuron/Sections/Dapp/Native/DAppDeviceMotionMessageHandler.swift rename to Neuron/Sections/Dapp/Handler/DAppDeviceMotionMessageHandler.swift diff --git a/Neuron/Sections/Dapp/Native/DAppGyroscopeMessageHandler.swift b/Neuron/Sections/Dapp/Handler/DAppGyroscopeMessageHandler.swift similarity index 100% rename from Neuron/Sections/Dapp/Native/DAppGyroscopeMessageHandler.swift rename to Neuron/Sections/Dapp/Handler/DAppGyroscopeMessageHandler.swift diff --git a/Neuron/Sections/Dapp/Native/DAppNativeMessageHandler.swift b/Neuron/Sections/Dapp/Handler/DAppNativeMessageHandler.swift similarity index 100% rename from Neuron/Sections/Dapp/Native/DAppNativeMessageHandler.swift rename to Neuron/Sections/Dapp/Handler/DAppNativeMessageHandler.swift diff --git a/Neuron/Sections/Dapp/Native/DAppQRCodeMessageHandler.swift b/Neuron/Sections/Dapp/Handler/DAppQRCodeMessageHandler.swift similarity index 100% rename from Neuron/Sections/Dapp/Native/DAppQRCodeMessageHandler.swift rename to Neuron/Sections/Dapp/Handler/DAppQRCodeMessageHandler.swift diff --git a/Neuron/Sections/Dapp/Native/DAppTakePhotoMessageHandler.swift b/Neuron/Sections/Dapp/Handler/DAppTakePhotoMessageHandler.swift similarity index 100% rename from Neuron/Sections/Dapp/Native/DAppTakePhotoMessageHandler.swift rename to Neuron/Sections/Dapp/Handler/DAppTakePhotoMessageHandler.swift diff --git a/Neuron/Sections/Dapp/WebView/MyDAppViewController.swift b/Neuron/Sections/Dapp/Native/MyDAppViewController.swift similarity index 98% rename from Neuron/Sections/Dapp/WebView/MyDAppViewController.swift rename to Neuron/Sections/Dapp/Native/MyDAppViewController.swift index 98f258c0..45799a02 100644 --- a/Neuron/Sections/Dapp/WebView/MyDAppViewController.swift +++ b/Neuron/Sections/Dapp/Native/MyDAppViewController.swift @@ -24,7 +24,7 @@ class MyDAppViewController: UITableViewController, ErrorOverlayPresentable { let realm = try! Realm() let result = realm.objects(DAppModel.self) if result.count == 0 { - showOverlay() + showBlankOverlay() } else { collections.append(contentsOf: result) tableView.reloadData() diff --git a/Neuron/Sections/Wallet/Home/NFTDetail.storyboard b/Neuron/Sections/Dapp/Native/NFTDetail.storyboard similarity index 100% rename from Neuron/Sections/Wallet/Home/NFTDetail.storyboard rename to Neuron/Sections/Dapp/Native/NFTDetail.storyboard diff --git a/Neuron/Sections/Wallet/Home/NFTDetailViewController.swift b/Neuron/Sections/Dapp/Native/NFTDetailViewController.swift similarity index 100% rename from Neuron/Sections/Wallet/Home/NFTDetailViewController.swift rename to Neuron/Sections/Dapp/Native/NFTDetailViewController.swift diff --git a/Neuron/Sections/Wallet/Home/NFTFooterReusableView.swift b/Neuron/Sections/Dapp/Native/NFTFooterReusableView.swift similarity index 100% rename from Neuron/Sections/Wallet/Home/NFTFooterReusableView.swift rename to Neuron/Sections/Dapp/Native/NFTFooterReusableView.swift diff --git a/Neuron/Sections/Wallet/Home/NFTHeaderReusableView.swift b/Neuron/Sections/Dapp/Native/NFTHeaderReusableView.swift similarity index 100% rename from Neuron/Sections/Wallet/Home/NFTHeaderReusableView.swift rename to Neuron/Sections/Dapp/Native/NFTHeaderReusableView.swift diff --git a/Neuron/Sections/Wallet/Home/NFTViewController.swift b/Neuron/Sections/Dapp/Native/NFTViewController.swift similarity index 77% rename from Neuron/Sections/Wallet/Home/NFTViewController.swift rename to Neuron/Sections/Dapp/Native/NFTViewController.swift index f4c0a135..0c8219da 100644 --- a/Neuron/Sections/Wallet/Home/NFTViewController.swift +++ b/Neuron/Sections/Dapp/Native/NFTViewController.swift @@ -14,17 +14,11 @@ class NFTViewController: UITableViewController, ErrorOverlayPresentable { override func viewDidLoad() { super.viewDidLoad() + title = "我的藏品" getListData() - addNotify() } - func addNotify() { - NotificationCenter.default.addObserver(self, selector: #selector(refreshData), name: .beginRefresh, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(refreshData), name: .switchWallet, object: nil) - } - - @objc - func refreshData() { + @IBAction func refresh(_ sender: UIRefreshControl) { getListData() } @@ -47,6 +41,7 @@ class NFTViewController: UITableViewController, ErrorOverlayPresentable { self.showNetworkFailOverlay() } self.tableView.reloadData() + self.tableView.refreshControl?.endRefreshing() } } @@ -71,15 +66,4 @@ class NFTViewController: UITableViewController, ErrorOverlayPresentable { nftDetailViewController.assetsModel = model navigationController?.pushViewController(nftDetailViewController, animated: true) } - - override func scrollViewDidScroll(_ scrollView: UIScrollView) { - var offset = scrollView.contentOffset.y - if #available(iOS 11.0, *) { - offset += scrollView.adjustedContentInset.top - } else { - offset += scrollView.contentInset.top - } - - tableView.isScrollEnabled = offset > 0 - } } diff --git a/Neuron/Sections/Dapp/WebView/DappViewController.swift b/Neuron/Sections/Dapp/WebView/DappViewController.swift index d322b4ce..9ff79447 100644 --- a/Neuron/Sections/Dapp/WebView/DappViewController.swift +++ b/Neuron/Sections/Dapp/WebView/DappViewController.swift @@ -105,7 +105,8 @@ extension DappViewController: WKScriptMessageHandler { let myDAppViewController: MyDAppViewController = UIStoryboard(name: .dAppBrowser).instantiateViewController() self.navigationController?.pushViewController(myDAppViewController, animated: true) case "pushCollectionView": - Toast.showToast(text: "敬请期待") + let nftViewController = UIStoryboard(name: "Wallet", bundle: nil).instantiateViewController(withIdentifier: "nftViewController") + self.navigationController?.pushViewController(nftViewController, animated: true) default: break } diff --git a/Neuron/Sections/Wallet/Home/Wallet.storyboard b/Neuron/Sections/Wallet/Home/Wallet.storyboard index 261518fe..572ad2a3 100644 --- a/Neuron/Sections/Wallet/Home/Wallet.storyboard +++ b/Neuron/Sections/Wallet/Home/Wallet.storyboard @@ -14,7 +14,7 @@ - + @@ -84,6 +84,12 @@ + + + + + + diff --git a/Neuron/Utils/OverlayPresentable.swift b/Neuron/Utils/OverlayPresentable.swift index a6a91cba..71aca8ff 100644 --- a/Neuron/Utils/OverlayPresentable.swift +++ b/Neuron/Utils/OverlayPresentable.swift @@ -28,6 +28,17 @@ extension OverlayPresentable where Self: UIViewController { } } +extension OverlayPresentable where Self: UITableViewController { + func showOverlay() { + overlay.frame = tableView.frame + tableView.addSubview(overlay) + } + + func removeOverlay() { + overlay.removeFromSuperview() + } +} + // MARK: - Error class ErrorOverlayViewController: UIViewController { From 81b21a95689bad7c3cda6d0da73fafc9f16549a9 Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 26 Nov 2018 16:57:39 +0900 Subject: [PATCH 217/315] Localize CustomTokenError --- Neuron/Services/Common/ServiceErrors.swift | 9 ++++++++- Neuron/en.lproj/Localizable.strings | 11 +++++++++-- Neuron/zh-Hans.lproj/Localizable.strings | 11 +++++++++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Neuron/Services/Common/ServiceErrors.swift b/Neuron/Services/Common/ServiceErrors.swift index 6b50ed09..13a5e676 100644 --- a/Neuron/Services/Common/ServiceErrors.swift +++ b/Neuron/Services/Common/ServiceErrors.swift @@ -8,23 +8,30 @@ import Foundation +// TODO: Remove result type enum EthServiceResult { case success(T) case error(Error) } +// TODO: Remove result type enum AppChainServiceResult { case success(T) case error(Error) } -enum CustomTokenError: Error { +enum CustomTokenError: String, LocalizedError { case wrongBalanceError case badNameError case badSymbolError case undefinedError + + var errorDescription: String? { + return NSLocalizedString("CustomTokenError.\(rawValue)", comment: "") + } } +// TODO: Remove result type enum SignMessageResult { case success(T) case error(Error) diff --git a/Neuron/en.lproj/Localizable.strings b/Neuron/en.lproj/Localizable.strings index 67aad6fd..a6677898 100644 --- a/Neuron/en.lproj/Localizable.strings +++ b/Neuron/en.lproj/Localizable.strings @@ -7,7 +7,7 @@ */ // Wallet Manager Errors - +// --------------------------------------------------------- "WalletManager.Error.invalidPassword" = "Invalid password"; "WalletManager.Error.invalidPrivateKey" = "Invalid Private Key"; "WalletManager.Error.invalidKeystore" = "Invalid Keystore"; @@ -20,7 +20,7 @@ "WalletManager.Error.unknown" = "Unknow error"; // Send Transactoin Error - +// --------------------------------------------------------- "SendTransactionError.invalidSourceAddress" = "Invalid source address"; "SendTransactionError.invalidDestinationAddress" = "Invalid destination address"; "SendTransactionError.invalidContractAddress" = "Invalid contract ADdress"; @@ -28,3 +28,10 @@ "SendTransactionError.createTransactionIssue" = "Failed to create transaction"; "SendTransactionError.invalidChainId" = "Invalid chain ID"; "SendTransactionError.signTXFailed" = "Failed to sign the transaction"; + +// Custom Token Error +// --------------------------------------------------------- +"CustomTokenError.wrongBalanceError" = "Wrong balance"; +"CustomTokenError.badNameError" = "Bad token name"; +"CustomTokenError.badSymbolError" = "Bad token symbol"; +"CustomTokenError.undefinedError" = "Undefined error"; diff --git a/Neuron/zh-Hans.lproj/Localizable.strings b/Neuron/zh-Hans.lproj/Localizable.strings index 2592cc8f..505cc6fa 100644 --- a/Neuron/zh-Hans.lproj/Localizable.strings +++ b/Neuron/zh-Hans.lproj/Localizable.strings @@ -7,7 +7,7 @@ */ // Wallet Manager Errors - +// --------------------------------------------------------- "WalletManager.Error.invalidPassword" = "密码不正确"; "WalletManager.Error.invalidPrivateKey" = "私钥不正确"; "WalletManager.Error.invalidKeystore" = "Keystore不正确"; @@ -20,7 +20,7 @@ "WalletManager.Error.unknown" = "未知错误"; // Send Transactoin Error - +// --------------------------------------------------------- "SendTransactionError.invalidSourceAddress" = "发送地址不正确"; "SendTransactionError.invalidDestinationAddress" = "接收地址不正确"; "SendTransactionError.invalidContractAddress" = "合约地址不正确"; @@ -28,3 +28,10 @@ "SendTransactionError.createTransactionIssue" = "创建交易失败"; "SendTransactionError.invalidChainId" = "Chain ID不正确"; "SendTransactionError.signTXFailed" = "交易签名失败"; + +// Custom Token Error +// --------------------------------------------------------- +"CustomTokenError.wrongBalanceError" = "余额不正确"; +"CustomTokenError.badNameError" = "Token名称不正确"; +"CustomTokenError.badSymbolError" = "Token symbol不正确"; +"CustomTokenError.undefinedError" = "未知错误"; From f596f664d163a6d2aee4882900a59f508d1a5fe2 Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 26 Nov 2018 17:12:23 +0900 Subject: [PATCH 218/315] Add String.localized(_ comment:) --- Neuron/Extensions/Foundation/String+Extension.swift | 4 ++++ Neuron/Services/Common/ServiceErrors.swift | 4 ++-- Neuron/Services/WalletManager/WalletManager+Errors.swift | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Neuron/Extensions/Foundation/String+Extension.swift b/Neuron/Extensions/Foundation/String+Extension.swift index be51f1fe..3c0c2499 100644 --- a/Neuron/Extensions/Foundation/String+Extension.swift +++ b/Neuron/Extensions/Foundation/String+Extension.swift @@ -23,4 +23,8 @@ extension String { } return "0x" + self } + + func localized(_ comment: String = "") -> String { + return NSLocalizedString(self, comment: comment) + } } diff --git a/Neuron/Services/Common/ServiceErrors.swift b/Neuron/Services/Common/ServiceErrors.swift index 13a5e676..2695d2e0 100644 --- a/Neuron/Services/Common/ServiceErrors.swift +++ b/Neuron/Services/Common/ServiceErrors.swift @@ -27,7 +27,7 @@ enum CustomTokenError: String, LocalizedError { case undefinedError var errorDescription: String? { - return NSLocalizedString("CustomTokenError.\(rawValue)", comment: "") + return "CustomTokenError.\(rawValue)".localized() } } @@ -48,6 +48,6 @@ enum SendTransactionError: String, LocalizedError { case signTXFailed var errorDescription: String? { - return NSLocalizedString("SendTransactionError.\(rawValue)", comment: "") + return "SendTransactionError.\(rawValue)".localized() } } diff --git a/Neuron/Services/WalletManager/WalletManager+Errors.swift b/Neuron/Services/WalletManager/WalletManager+Errors.swift index ea49285e..18b81aa1 100644 --- a/Neuron/Services/WalletManager/WalletManager+Errors.swift +++ b/Neuron/Services/WalletManager/WalletManager+Errors.swift @@ -22,7 +22,7 @@ extension WalletManager { case unknown var errorDescription: String? { - return NSLocalizedString("WalletManager.Error.\(rawValue)", comment: "") + return "WalletManager.Error.\(rawValue)".localized() } } } From 52e4ece0f46be8f8694ef098c5ac9288b5869a30 Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 26 Nov 2018 17:23:27 +0900 Subject: [PATCH 219/315] Apply i18n to DApp view controller --- Neuron/Sections/Dapp/WebView/DappViewController.swift | 6 +++--- Neuron/en.lproj/Localizable.strings | 9 +++++++++ Neuron/zh-Hans.lproj/Localizable.strings | 9 +++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Neuron/Sections/Dapp/WebView/DappViewController.swift b/Neuron/Sections/Dapp/WebView/DappViewController.swift index d322b4ce..29ec66dc 100644 --- a/Neuron/Sections/Dapp/WebView/DappViewController.swift +++ b/Neuron/Sections/Dapp/WebView/DappViewController.swift @@ -23,7 +23,7 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, override func viewDidLoad() { super.viewDidLoad() - self.title = "应用" + self.title = "DApp.Home".localized() addWebView() layoutWebView() @@ -87,9 +87,9 @@ class DappViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, let error = error as NSError errorOverlaycontroller.style = .networkFail if error.code == -1009 { - errorOverlaycontroller.messageLabel.text = "似乎已断开与互联网的连接" + errorOverlaycontroller.messageLabel.text = "Common.Connection.ConnectionLost".localized() } else { - errorOverlaycontroller.messageLabel.text = "页面加载失败" + errorOverlaycontroller.messageLabel.text = "Common.Connection.FailToLoadPage".localized() } showOverlay() } diff --git a/Neuron/en.lproj/Localizable.strings b/Neuron/en.lproj/Localizable.strings index a6677898..b61cb107 100644 --- a/Neuron/en.lproj/Localizable.strings +++ b/Neuron/en.lproj/Localizable.strings @@ -6,6 +6,15 @@ Copyright © 2018 Cryptape. All rights reserved. */ +// Common +// --------------------------------------------------------- +"Common.Connection.ConnectionLost" = "The connection to the Internet is broken"; +"Common.Connection.FailToLoadPage" = "Failed to load the page"; + +// DApp +// --------------------------------------------------------- +"DApp.Home" = "DApps"; + // Wallet Manager Errors // --------------------------------------------------------- "WalletManager.Error.invalidPassword" = "Invalid password"; diff --git a/Neuron/zh-Hans.lproj/Localizable.strings b/Neuron/zh-Hans.lproj/Localizable.strings index 505cc6fa..e4ca08bd 100644 --- a/Neuron/zh-Hans.lproj/Localizable.strings +++ b/Neuron/zh-Hans.lproj/Localizable.strings @@ -6,6 +6,15 @@ Copyright © 2018 Cryptape. All rights reserved. */ +// Common +// --------------------------------------------------------- +"Common.Connection.ConnectionLost" = "似乎已断开与互联网的连接"; +"Common.Connection.FailToLoadPage" = "页面加载失败"; + +// DApp +// --------------------------------------------------------- +"DApp.Home" = "应用"; + // Wallet Manager Errors // --------------------------------------------------------- "WalletManager.Error.invalidPassword" = "密码不正确"; From 2be2a11f8d881423a44776cbba69282fd14c2fbb Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 26 Nov 2018 18:08:19 +0900 Subject: [PATCH 220/315] Move UIKit protocols/helpers to Utils, delete ShareItem class --- Neuron.xcodeproj/project.pbxproj | 24 ++++++++++------- Neuron/Utils/ShareItem.swift | 27 ------------------- .../Utils/{ => UIKit}/FixSwipeBackable.swift | 0 Neuron/Utils/{ => UIKit}/NoScreenshot.swift | 0 Neuron/Utils/{ => UIKit}/Overlay.storyboard | 0 .../{ => UIKit}/OverlayPresentable.swift | 0 Neuron/Utils/{ => UIKit}/Toast.swift | 0 .../Utils/{ => UIKit}/ToastActivityView.xib | 0 8 files changed, 14 insertions(+), 37 deletions(-) delete mode 100644 Neuron/Utils/ShareItem.swift rename Neuron/Utils/{ => UIKit}/FixSwipeBackable.swift (100%) rename Neuron/Utils/{ => UIKit}/NoScreenshot.swift (100%) rename Neuron/Utils/{ => UIKit}/Overlay.storyboard (100%) rename Neuron/Utils/{ => UIKit}/OverlayPresentable.swift (100%) rename Neuron/Utils/{ => UIKit}/Toast.swift (100%) rename Neuron/Utils/{ => UIKit}/ToastActivityView.xib (100%) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 1fad582f..a9158027 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -175,7 +175,6 @@ 89C614592140FE6600DC3DF4 /* CurrencyTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C614582140FE6600DC3DF4 /* CurrencyTableViewCell.swift */; }; 89C6145E2141038300DC3DF4 /* LocalCurrencyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C6145D2141038300DC3DF4 /* LocalCurrencyService.swift */; }; 89C614602141421400DC3DF4 /* TransactionHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C6145F2141421400DC3DF4 /* TransactionHistoryViewController.swift */; }; - 89C6E09920E07F8E00C94026 /* ShareItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C6E09820E07F8E00C94026 /* ShareItem.swift */; }; 89CA1FAC213F822B00669B2C /* tokens-list.json in Resources */ = {isa = PBXBuildFile; fileRef = 89CA1FAB213F822B00669B2C /* tokens-list.json */; }; 89CA1FAE213F832800669B2C /* CurrencyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CA1FAD213F832800669B2C /* CurrencyService.swift */; }; 89CFA12621424CAB00635A54 /* AboutUsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CFA12521424CAB00635A54 /* AboutUsViewController.swift */; }; @@ -398,7 +397,6 @@ 89C614582140FE6600DC3DF4 /* CurrencyTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyTableViewCell.swift; sourceTree = ""; }; 89C6145D2141038300DC3DF4 /* LocalCurrencyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalCurrencyService.swift; sourceTree = ""; }; 89C6145F2141421400DC3DF4 /* TransactionHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistoryViewController.swift; sourceTree = ""; }; - 89C6E09820E07F8E00C94026 /* ShareItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareItem.swift; sourceTree = ""; }; 89CA1FAB213F822B00669B2C /* tokens-list.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "tokens-list.json"; sourceTree = ""; }; 89CA1FAD213F832800669B2C /* CurrencyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyService.swift; sourceTree = ""; }; 89CFA12521424CAB00635A54 /* AboutUsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutUsViewController.swift; sourceTree = ""; }; @@ -456,6 +454,19 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1A14CF3D21ABEF1600AE6227 /* UIKit */ = { + isa = PBXGroup; + children = ( + 6E867E0C216C92A900BD6FE5 /* OverlayPresentable.swift */, + 6E859CB72176DC3100637E1C /* Overlay.storyboard */, + 6E9F9D5E219ABDE7009ED8B2 /* FixSwipeBackable.swift */, + 1A9204E321634212004B54DC /* ToastActivityView.xib */, + 8913437420C7D3A400A17AEF /* Toast.swift */, + 6E867E08216C862900BD6FE5 /* NoScreenshot.swift */, + ); + path = UIKit; + sourceTree = ""; + }; 1A1E8414217178A800B15E88 /* Validators */ = { isa = PBXGroup; children = ( @@ -815,17 +826,11 @@ 8928C23120AE84AB00C3103E /* Utils */ = { isa = PBXGroup; children = ( + 1A14CF3D21ABEF1600AE6227 /* UIKit */, 1A1E8414217178A800B15E88 /* Validators */, 89FC542C20D4142D00D5D27C /* SkipBackupFiles.swift */, - 1A9204E321634212004B54DC /* ToastActivityView.xib */, - 8913437420C7D3A400A17AEF /* Toast.swift */, - 89C6E09820E07F8E00C94026 /* ShareItem.swift */, - 6E867E08216C862900BD6FE5 /* NoScreenshot.swift */, 1A60B6FB2186DB1C00FEFB3A /* Web3Utils.swift */, 1A69465F2192A0B0000093A2 /* GasCalculator.swift */, - 6E867E0C216C92A900BD6FE5 /* OverlayPresentable.swift */, - 6E859CB72176DC3100637E1C /* Overlay.storyboard */, - 6E9F9D5E219ABDE7009ED8B2 /* FixSwipeBackable.swift */, ); path = Utils; sourceTree = ""; @@ -1505,7 +1510,6 @@ 8928C24920AE91E600C3103E /* DappViewController.swift in Sources */, 898CA98820EA23940059ECA3 /* EthNativeTokenService.swift in Sources */, 1A6007A021A29C2C00C7B712 /* SuccessPageItem.swift in Sources */, - 89C6E09920E07F8E00C94026 /* ShareItem.swift in Sources */, 6E867E09216C862900BD6FE5 /* NoScreenshot.swift in Sources */, 89C614592140FE6600DC3DF4 /* CurrencyTableViewCell.swift in Sources */, 8972FD0F218B54A100C27147 /* AdvancedViewController.swift in Sources */, diff --git a/Neuron/Utils/ShareItem.swift b/Neuron/Utils/ShareItem.swift deleted file mode 100644 index c095cd0c..00000000 --- a/Neuron/Utils/ShareItem.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// ShareItem.swift -// Neuron -// -// Created by XiaoLu on 2018/6/25. -// Copyright © 2018年 cryptape. All rights reserved. -// - -import UIKit - -class ShareItem: NSObject, UIActivityItemSource { - - var shareString: String - - init(shareString: String) { - self.shareString = shareString - } - - func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { - return shareString - } - - func activityViewController(_ activityViewController: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { - return shareString - } - -} diff --git a/Neuron/Utils/FixSwipeBackable.swift b/Neuron/Utils/UIKit/FixSwipeBackable.swift similarity index 100% rename from Neuron/Utils/FixSwipeBackable.swift rename to Neuron/Utils/UIKit/FixSwipeBackable.swift diff --git a/Neuron/Utils/NoScreenshot.swift b/Neuron/Utils/UIKit/NoScreenshot.swift similarity index 100% rename from Neuron/Utils/NoScreenshot.swift rename to Neuron/Utils/UIKit/NoScreenshot.swift diff --git a/Neuron/Utils/Overlay.storyboard b/Neuron/Utils/UIKit/Overlay.storyboard similarity index 100% rename from Neuron/Utils/Overlay.storyboard rename to Neuron/Utils/UIKit/Overlay.storyboard diff --git a/Neuron/Utils/OverlayPresentable.swift b/Neuron/Utils/UIKit/OverlayPresentable.swift similarity index 100% rename from Neuron/Utils/OverlayPresentable.swift rename to Neuron/Utils/UIKit/OverlayPresentable.swift diff --git a/Neuron/Utils/Toast.swift b/Neuron/Utils/UIKit/Toast.swift similarity index 100% rename from Neuron/Utils/Toast.swift rename to Neuron/Utils/UIKit/Toast.swift diff --git a/Neuron/Utils/ToastActivityView.xib b/Neuron/Utils/UIKit/ToastActivityView.xib similarity index 100% rename from Neuron/Utils/ToastActivityView.xib rename to Neuron/Utils/UIKit/ToastActivityView.xib From af8a92dcd3a04f71938c4da683821f36e5965137 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 26 Nov 2018 17:16:22 +0800 Subject: [PATCH 221/315] Fix erc20 blance issue --- Neuron/Models/Token.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Neuron/Models/Token.swift b/Neuron/Models/Token.swift index 873bdc07..5326c75d 100644 --- a/Neuron/Models/Token.swift +++ b/Neuron/Models/Token.swift @@ -30,6 +30,7 @@ class Token { var isNativeToken: Bool var walletAddress = "" var type: TokenType + var decimals = 18 let identifier: String var tokenModel: TokenModel { @@ -56,6 +57,7 @@ class Token { chainHosts = token.chainHosts isNativeToken = token.isNativeToken type = token.type + decimals = token.decimals identifier = token.identifier } @@ -82,7 +84,8 @@ class Token { let result = try contract.method("balanceOf", parameters: [walletAddress as AnyObject])?.call() balance = result?["0"] as! BigUInt } - let balanceText = Web3Utils.formatToEthereumUnits(balance, toUnits: .eth, decimals: 8) ?? "0" +// let balanceText = Web3Utils.formatToEthereumUnits(balance, toUnits: .eth, decimals: 8) ?? "0" + let balanceText = Web3Utils.formatToPrecision(balance, numberDecimals: decimals, formattingDecimals: 6) ?? "0" self.balance = Double(balanceText) refreshBalanceSignal?.leave() refreshBalanceSignal = nil From 4c1ff421c77f72487c74461535b10e059229cb60 Mon Sep 17 00:00:00 2001 From: LuFP Date: Mon, 26 Nov 2018 17:17:01 +0800 Subject: [PATCH 222/315] Remove useless notification --- Neuron/Constants/NotificationName.swift | 5 ----- .../Wallet/AddWallet/VerifyMnemonicViewController.swift | 5 ----- .../Wallet/ImportWallet/KeystoreViewController.swift | 5 ----- .../Wallet/ImportWallet/MnemonicViewController.swift | 5 ----- .../Wallet/ImportWallet/PrivatekeyViewController.swift | 5 ----- .../Wallet/WalletDetails/WalletDetailController.swift | 3 --- 6 files changed, 28 deletions(-) diff --git a/Neuron/Constants/NotificationName.swift b/Neuron/Constants/NotificationName.swift index af7712cd..cc7c062c 100644 --- a/Neuron/Constants/NotificationName.swift +++ b/Neuron/Constants/NotificationName.swift @@ -9,12 +9,7 @@ import Foundation extension Notification.Name { - static let createWalletSuccess = Notification.Name("createWalletSuccess") - static let firstWalletCreated = Notification.Name("firstWalletCreated") - static let allWalletsDeleted = Notification.Name("allWalletsDeleted") static let changeLocalCurrency = Notification.Name("changeLocalCurrency") static let switchEthNetwork = Notification.Name("switchEthNetwork") static let switchWallet = Notification.Name("switchWallet") - static let beginRefresh = Notification.Name("beginRefresh") - static let endRefresh = Notification.Name("endRefresh") } diff --git a/Neuron/Sections/Wallet/AddWallet/VerifyMnemonicViewController.swift b/Neuron/Sections/Wallet/AddWallet/VerifyMnemonicViewController.swift index 1435258b..734bef32 100644 --- a/Neuron/Sections/Wallet/AddWallet/VerifyMnemonicViewController.swift +++ b/Neuron/Sections/Wallet/AddWallet/VerifyMnemonicViewController.swift @@ -132,7 +132,6 @@ class VerifyMnemonicViewController: UIViewController, ButtonTagViewDelegate, But private func saveWalletToRealm() { let appModel = AppModel.current - let isFirstWallet = appModel.wallets.count == 0 let iconImage = GitHubIdenticon().icon(from: walletModel.address.lowercased(), size: CGSize(width: 60, height: 60)) walletModel.iconData = iconImage!.pngData() let realm = try! Realm() @@ -142,10 +141,6 @@ class VerifyMnemonicViewController: UIViewController, ButtonTagViewDelegate, But realm.add(appModel) } navigationController?.popToRootViewController(animated: true) - if isFirstWallet { - NotificationCenter.default.post(name: .firstWalletCreated, object: nil) - } - NotificationCenter.default.post(name: .createWalletSuccess, object: nil, userInfo: ["address": walletModel.address]) Toast.showToast(text: "创建成功") } } diff --git a/Neuron/Sections/Wallet/ImportWallet/KeystoreViewController.swift b/Neuron/Sections/Wallet/ImportWallet/KeystoreViewController.swift index d04f8d83..9310e040 100644 --- a/Neuron/Sections/Wallet/ImportWallet/KeystoreViewController.swift +++ b/Neuron/Sections/Wallet/ImportWallet/KeystoreViewController.swift @@ -113,7 +113,6 @@ class KeystoreViewController: UITableViewController, QRCodeViewControllerDelegat return } - let isFirstWallet = appModel.wallets.count == 0 let iconImage = GitHubIdenticon().icon(from: walletModel.address.lowercased(), size: CGSize(width: 60, height: 60)) walletModel.iconData = iconImage!.pngData()! do { @@ -125,10 +124,6 @@ class KeystoreViewController: UITableViewController, QRCodeViewControllerDelegat } Toast.showToast(text: "导入成功") SensorsAnalytics.Track.importWallet(type: .keystore, address: walletModel.address) - if isFirstWallet { - NotificationCenter.default.post(name: .firstWalletCreated, object: nil) - } - NotificationCenter.default.post(name: .createWalletSuccess, object: nil, userInfo: ["address": walletModel.address]) navigationController?.popToRootViewController(animated: true) } catch { Toast.showToast(text: error.localizedDescription) diff --git a/Neuron/Sections/Wallet/ImportWallet/MnemonicViewController.swift b/Neuron/Sections/Wallet/ImportWallet/MnemonicViewController.swift index 46e838a5..4faf7a17 100644 --- a/Neuron/Sections/Wallet/ImportWallet/MnemonicViewController.swift +++ b/Neuron/Sections/Wallet/ImportWallet/MnemonicViewController.swift @@ -127,7 +127,6 @@ class MnemonicViewController: UITableViewController, QRCodeViewControllerDelegat return } - let isFirstWallet = appModel.wallets.count == 0 let iconImage = GitHubIdenticon().icon(from: walletModel.address.lowercased(), size: CGSize(width: 60, height: 60)) walletModel.iconData = iconImage!.pngData()! do { @@ -139,10 +138,6 @@ class MnemonicViewController: UITableViewController, QRCodeViewControllerDelegat } Toast.showToast(text: "导入成功") SensorsAnalytics.Track.importWallet(type: .keystore, address: walletModel.address) - if isFirstWallet { - NotificationCenter.default.post(name: .firstWalletCreated, object: nil) - } - NotificationCenter.default.post(name: .createWalletSuccess, object: nil, userInfo: ["address": walletModel.address]) navigationController?.popToRootViewController(animated: true) } catch { Toast.showToast(text: error.localizedDescription) diff --git a/Neuron/Sections/Wallet/ImportWallet/PrivatekeyViewController.swift b/Neuron/Sections/Wallet/ImportWallet/PrivatekeyViewController.swift index a49f1dd1..dc7cf452 100644 --- a/Neuron/Sections/Wallet/ImportWallet/PrivatekeyViewController.swift +++ b/Neuron/Sections/Wallet/ImportWallet/PrivatekeyViewController.swift @@ -124,7 +124,6 @@ class PrivatekeyViewController: UITableViewController, QRCodeViewControllerDeleg return } - let isFirstWallet = appModel.wallets.count == 0 let iconImage = GitHubIdenticon().icon(from: walletModel.address.lowercased(), size: CGSize(width: 60, height: 60)) walletModel.iconData = iconImage!.pngData()! do { @@ -136,10 +135,6 @@ class PrivatekeyViewController: UITableViewController, QRCodeViewControllerDeleg } Toast.showToast(text: "导入成功") SensorsAnalytics.Track.importWallet(type: .keystore, address: walletModel.address) - if isFirstWallet { - NotificationCenter.default.post(name: .firstWalletCreated, object: nil) - } - NotificationCenter.default.post(name: .createWalletSuccess, object: nil, userInfo: ["address": walletModel.address]) navigationController?.popToRootViewController(animated: true) } catch { Toast.showToast(text: error.localizedDescription) diff --git a/Neuron/Sections/Wallet/WalletDetails/WalletDetailController.swift b/Neuron/Sections/Wallet/WalletDetails/WalletDetailController.swift index 7129a4a6..2b1d5b59 100644 --- a/Neuron/Sections/Wallet/WalletDetails/WalletDetailController.swift +++ b/Neuron/Sections/Wallet/WalletDetails/WalletDetailController.swift @@ -130,9 +130,6 @@ class WalletDetailController: UITableViewController { realm.delete(self.walletModel) appItem.currentWallet = appItem.wallets.first } - if AppModel.current.wallets.isEmpty { - NotificationCenter.default.post(name: .allWalletsDeleted, object: nil) - } Toast.showToast(text: "删除成功") deleteBulletinManager?.dismissBulletin() self.navigationController?.popViewController(animated: true) From 4a645732ce739a2bd182f40ae4e064eb00628567 Mon Sep 17 00:00:00 2001 From: LuFP Date: Mon, 26 Nov 2018 17:25:11 +0800 Subject: [PATCH 223/315] Delete switch wallet --- Neuron/Constants/NotificationName.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Neuron/Constants/NotificationName.swift b/Neuron/Constants/NotificationName.swift index cc7c062c..e2f886d4 100644 --- a/Neuron/Constants/NotificationName.swift +++ b/Neuron/Constants/NotificationName.swift @@ -11,5 +11,4 @@ import Foundation extension Notification.Name { static let changeLocalCurrency = Notification.Name("changeLocalCurrency") static let switchEthNetwork = Notification.Name("switchEthNetwork") - static let switchWallet = Notification.Name("switchWallet") } From cbc18717a1beef1c0fb190f659231a4ac07b71cb Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 26 Nov 2018 17:26:02 +0800 Subject: [PATCH 224/315] Fix erc20 value issue --- Neuron/Models/TransactionDetails.swift | 1 + Neuron/Sections/Transaction/TransactionHistoryPresenter.swift | 3 +++ .../Transaction/TransactionHistoryViewController.swift | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Neuron/Models/TransactionDetails.swift b/Neuron/Models/TransactionDetails.swift index 4f0cf52d..087b28b8 100644 --- a/Neuron/Models/TransactionDetails.swift +++ b/Neuron/Models/TransactionDetails.swift @@ -27,6 +27,7 @@ class TransactionDetails: Codable { var date = Date() var blockNumber: BigUInt = 0 var status: TransactionState = .success + var token: Token! enum CodingKeys: String, CodingKey { case hash diff --git a/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift b/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift index 66ae2354..2af3aac2 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift @@ -55,6 +55,9 @@ class TransactionHistoryPresenter: NSObject, TransactionStatusManagerDelegate { // merge list = self.mergeSentTransactions(from: list) + list.forEach({ (trans) in + trans.token = self.token + }) var insertions = [Int]() for idx in list.indices { diff --git a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift index 13519526..d20f1caf 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift @@ -226,7 +226,7 @@ class TransactionHistoryTableViewCell: UITableViewCell { dateLabel.text = dateformatter.string(from: transaction.date) let walletAddress = AppModel.current.currentWallet!.address - let amount = Web3.Utils.formatToEthereumUnits(transaction.value, toUnits: .eth, decimals: 8)! + let amount = Web3Utils.formatToPrecision(transaction.value, numberDecimals: transaction.token.decimals)! if transaction.from.lowercased() == walletAddress.lowercased() { addressLabel.text = transaction.from numberLabel.text = "-\(amount)" From d93bc55e8291a39865faebbed8d591bc385c5f25 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 26 Nov 2018 20:29:03 +0800 Subject: [PATCH 225/315] Transaction estimate gas limit --- .../Transaction/SendTransaction.storyboard | 18 ++++++----- ...ransactionGasCostTableViewController.swift | 30 +++++++++++++++++-- .../Transaction/TransactionParamBuilder.swift | 21 +++++++++++++ 3 files changed, 60 insertions(+), 9 deletions(-) diff --git a/Neuron/Sections/Transaction/SendTransaction.storyboard b/Neuron/Sections/Transaction/SendTransaction.storyboard index 6e0b682e..ed55f402 100644 --- a/Neuron/Sections/Transaction/SendTransaction.storyboard +++ b/Neuron/Sections/Transaction/SendTransaction.storyboard @@ -193,7 +193,7 @@ - + @@ -259,13 +259,13 @@ - - + @@ -868,7 +868,6 @@ - @@ -882,8 +881,8 @@ + - From ccada5cec2c02c92851b1e5cd215839f173c1e0d Mon Sep 17 00:00:00 2001 From: cezres Date: Wed, 28 Nov 2018 19:02:31 +0800 Subject: [PATCH 263/315] Fix change password check --- .../WalletDetails/ChangePasswordController.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Neuron/Sections/Wallet/WalletDetails/ChangePasswordController.swift b/Neuron/Sections/Wallet/WalletDetails/ChangePasswordController.swift index 6ce2e92e..47d9070d 100644 --- a/Neuron/Sections/Wallet/WalletDetails/ChangePasswordController.swift +++ b/Neuron/Sections/Wallet/WalletDetails/ChangePasswordController.swift @@ -34,7 +34,7 @@ class ChangePasswordController: UITableViewController, UITextFieldDelegate { if oldPassword.lengthOfBytes(using: .utf8) >= 8 && newPassword.lengthOfBytes(using: .utf8) >= 8 && - reNewPassword == newPassword { + reNewPassword.lengthOfBytes(using: .utf8) >= 8 { confirmButton.backgroundColor = UIColor(red: 80/255.0, green: 114/255.0, blue: 251/255.0, alpha: 1.0) confirmButton.isEnabled = true } else { @@ -45,10 +45,6 @@ class ChangePasswordController: UITableViewController, UITextFieldDelegate { } @IBAction func confirm(_ sender: Any) { - if case .invalid(let reason) = PasswordValidator.validate(password: newPasswordTextField.text!) { - Toast.showToast(text: reason) - return - } if oldPasswordTextField.text == newPasswordTextField.text { Toast.showToast(text: "您输入的密码和原密码一致,请重新输入") return @@ -57,6 +53,10 @@ class ChangePasswordController: UITableViewController, UITextFieldDelegate { Toast.showToast(text: "两次新密码输入不一致") return } + if case .invalid(let reason) = PasswordValidator.validate(password: newPasswordTextField.text!) { + Toast.showToast(text: reason) + return + } Toast.showHUD(text: "修改密码中...") let oldPassword = oldPasswordTextField.text! let newPassword = newPasswordTextField.text! From c205ab0b76ab3a4f92eaa376a33e43ecd9701c59 Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 29 Nov 2018 09:09:00 +0900 Subject: [PATCH 264/315] Fix typo in func name --- .../Wallet/WalletDetails/WalletDetailController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neuron/Sections/Wallet/WalletDetails/WalletDetailController.swift b/Neuron/Sections/Wallet/WalletDetails/WalletDetailController.swift index 2b1d5b59..690e26e4 100644 --- a/Neuron/Sections/Wallet/WalletDetails/WalletDetailController.swift +++ b/Neuron/Sections/Wallet/WalletDetails/WalletDetailController.swift @@ -33,7 +33,7 @@ class WalletDetailController: UITableViewController { walletIconImageView.image = UIImage(data: walletModel.iconData) } - func creatDeleteWalletPageItem() -> PasswordPageItem { + func createDeleteWalletPageItem() -> PasswordPageItem { let passwordPageItem = PasswordPageItem.create(title: "删除钱包", actionButtonTitle: "确认删除") passwordPageItem.actionHandler = { [weak self] item in @@ -71,7 +71,7 @@ class WalletDetailController: UITableViewController { } @IBAction func didDeleteWallet(_ sender: UIButton) { - deleteBulletinManager = BLTNItemManager(rootItem: creatDeleteWalletPageItem()) + deleteBulletinManager = BLTNItemManager(rootItem: createDeleteWalletPageItem()) deleteBulletinManager?.showBulletin(above: self) } From e1acece43adc96da97fb243c77cdafd6abbdfe1a Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 29 Nov 2018 09:13:11 +0900 Subject: [PATCH 265/315] Create keystore with aes-128-ctr mode as default MetaMask and several other wallets don't seem to support aes-128-cbc mode. --- Neuron/Services/WalletManager/WalletManager.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neuron/Services/WalletManager/WalletManager.swift b/Neuron/Services/WalletManager/WalletManager.swift index f6550e4d..390fc6c9 100755 --- a/Neuron/Services/WalletManager/WalletManager.swift +++ b/Neuron/Services/WalletManager/WalletManager.swift @@ -60,7 +60,7 @@ extension WalletManager { var privateKey = try bip32Keystore.UNSAFE_getPrivateKeyData(password: password, account: address) defer { Data.zero(&privateKey) } - guard let keystore = try EthereumKeystoreV3(privateKey: privateKey, password: password) else { + guard let keystore = try EthereumKeystoreV3(privateKey: privateKey, password: password, aesMode: "aes-128-ctr") else { throw Error.invalidMnemonic } @@ -87,7 +87,7 @@ extension WalletManager { func importPrivateKey(privateKey: String, password: String) throws -> Wallet { guard let data = Data.fromHex(privateKey.trimmingCharacters(in: .whitespacesAndNewlines)), - let keystore = try EthereumKeystoreV3(privateKey: data, password: password), + let keystore = try EthereumKeystoreV3(privateKey: data, password: password, aesMode: "aes-128-ctr"), let address = keystore.getAddress()?.address else { throw Error.invalidPrivateKey } From 588e1561504c34d7e2aaf94141b1e78b493a7a20 Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 29 Nov 2018 11:19:01 +0800 Subject: [PATCH 266/315] Fix guide window bug --- Neuron/Sections/Guide/Guide.storyboard | 5 ++++ Neuron/Sections/Guide/GuideService.swift | 26 ++++++++++++------- .../Sections/Guide/GuideViewController.swift | 6 ----- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/Neuron/Sections/Guide/Guide.storyboard b/Neuron/Sections/Guide/Guide.storyboard index 0e11f8f2..5d184255 100644 --- a/Neuron/Sections/Guide/Guide.storyboard +++ b/Neuron/Sections/Guide/Guide.storyboard @@ -86,6 +86,11 @@ + + + + + diff --git a/Neuron/Sections/Guide/GuideService.swift b/Neuron/Sections/Guide/GuideService.swift index 536b533f..23e8b85d 100644 --- a/Neuron/Sections/Guide/GuideService.swift +++ b/Neuron/Sections/Guide/GuideService.swift @@ -11,7 +11,7 @@ import RealmSwift class GuideService { static let shared = GuideService() - var controller: UIViewController? + private var window: UIWindow? private var notificationToken: NotificationToken? private init() { @@ -41,20 +41,28 @@ class GuideService { } private func showGuide() { - guard controller == nil else { return } + guard window == nil else { return } let guideController: GuideViewController = UIStoryboard(name: .guide).instantiateViewController() - controller = BaseNavigationController(rootViewController: guideController) - controller?.modalPresentationStyle = .overCurrentContext - UIApplication.shared.keyWindow?.rootViewController?.present(controller!, animated: true, completion: nil) + let controller = BaseNavigationController(rootViewController: guideController) + window = UIWindow(frame: UIScreen.main.bounds) + window?.rootViewController = controller + window?.makeKeyAndVisible() + + let height = window?.bounds.size.height ?? 0.0 + window?.transform = CGAffineTransform(translationX: 0, y: height) + UIView.animate(withDuration: CATransaction.animationDuration()) { + self.window?.transform = CGAffineTransform.identity + } } private func hideGuide() { UIView.animate(withDuration: CATransaction.animationDuration(), animations: { - let height = self.controller?.view.bounds.size.height ?? 0.0 - self.controller?.view.transform = CGAffineTransform(translationX: 0, y: height) + let height = self.window?.bounds.size.height ?? 0.0 + self.window?.transform = CGAffineTransform(translationX: 0, y: height) }, completion: { (_) in - self.controller?.dismiss(animated: false, completion: nil) - self.controller = nil + self.window?.rootViewController = nil + self.window?.resignKey() + self.window = nil }) } } diff --git a/Neuron/Sections/Guide/GuideViewController.swift b/Neuron/Sections/Guide/GuideViewController.swift index 28c05213..5f96e7d8 100644 --- a/Neuron/Sections/Guide/GuideViewController.swift +++ b/Neuron/Sections/Guide/GuideViewController.swift @@ -37,12 +37,6 @@ class GuideViewController: UIViewController, UICollectionViewDataSource, UIColle override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.setNavigationBarHidden(true, animated: animated) - UIApplication.shared.keyWindow?.windowLevel = .alert - } - - override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - UIApplication.shared.keyWindow?.windowLevel = .normal } @objc func showProductAgreementView() { From df89fb87ef62cd5c1710ae6e0a9c51e9a15381b9 Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 29 Nov 2018 11:19:44 +0800 Subject: [PATCH 267/315] Update send transaction storyboard --- Neuron/Sections/Transaction/SendTransaction.storyboard | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Neuron/Sections/Transaction/SendTransaction.storyboard b/Neuron/Sections/Transaction/SendTransaction.storyboard index 7ec5699c..b335ee7f 100644 --- a/Neuron/Sections/Transaction/SendTransaction.storyboard +++ b/Neuron/Sections/Transaction/SendTransaction.storyboard @@ -816,7 +816,7 @@ - + @@ -872,6 +872,7 @@ + From 209029b2a203b78d46175f33f66af685061ac900 Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 29 Nov 2018 11:54:44 +0800 Subject: [PATCH 268/315] Wallet load fail --- Neuron/Models/Token.swift | 41 ++++---- Neuron/Sections/Wallet/Home/Wallet.storyboard | 96 +++++++++++++++++++ .../Wallet/Home/WalletPresenter.swift | 6 +- .../Wallet/Home/WalletViewController.swift | 10 +- 4 files changed, 132 insertions(+), 21 deletions(-) diff --git a/Neuron/Models/Token.swift b/Neuron/Models/Token.swift index 5326c75d..8cbfe081 100644 --- a/Neuron/Models/Token.swift +++ b/Neuron/Models/Token.swift @@ -71,25 +71,32 @@ class Token { } refreshBalanceSignal = DispatchGroup() refreshBalanceSignal?.enter() + + defer { + refreshBalanceSignal?.leave() + refreshBalanceSignal = nil + } + let balance: BigUInt - switch type { - case .ether: - balance = try EthereumNetwork().getWeb3().eth.getBalance(address: EthereumAddress(walletAddress)!) - case .appChain, .appChainErc20: - balance = try AppChainNetwork.appChain(url: URL(string: chainHosts)).rpc.getBalance(address: walletAddress) - case .erc20: - let contractAddress = EthereumAddress(address)! - let walletAddress = EthereumAddress(self.walletAddress)! - let contract = EthereumNetwork().getWeb3().contract(Web3.Utils.erc20ABI, at: contractAddress, abiVersion: 2)! - let result = try contract.method("balanceOf", parameters: [walletAddress as AnyObject])?.call() - balance = result?["0"] as! BigUInt + do { + switch type { + case .ether: + balance = try EthereumNetwork().getWeb3().eth.getBalance(address: EthereumAddress(walletAddress)!) + case .appChain, .appChainErc20: + balance = try AppChainNetwork.appChain(url: URL(string: chainHosts)).rpc.getBalance(address: walletAddress) + case .erc20: + let contractAddress = EthereumAddress(address)! + let walletAddress = EthereumAddress(self.walletAddress)! + let contract = EthereumNetwork().getWeb3().contract(Web3.Utils.erc20ABI, at: contractAddress, abiVersion: 2)! + let result = try contract.method("balanceOf", parameters: [walletAddress as AnyObject])?.call() + balance = result?["0"] as! BigUInt + } + let balanceText = Web3Utils.formatToPrecision(balance, numberDecimals: decimals, formattingDecimals: 6) ?? "0" + self.balance = Double(balanceText) + return self.balance + } catch { + throw error } -// let balanceText = Web3Utils.formatToEthereumUnits(balance, toUnits: .eth, decimals: 8) ?? "0" - let balanceText = Web3Utils.formatToPrecision(balance, numberDecimals: decimals, formattingDecimals: 6) ?? "0" - self.balance = Double(balanceText) - refreshBalanceSignal?.leave() - refreshBalanceSignal = nil - return self.balance } } diff --git a/Neuron/Sections/Wallet/Home/Wallet.storyboard b/Neuron/Sections/Wallet/Home/Wallet.storyboard index 0e0c39fa..2615e3fd 100644 --- a/Neuron/Sections/Wallet/Home/Wallet.storyboard +++ b/Neuron/Sections/Wallet/Home/Wallet.storyboard @@ -591,6 +591,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Neuron/Sections/Wallet/Home/WalletPresenter.swift b/Neuron/Sections/Wallet/Home/WalletPresenter.swift index e301a2ab..b3b61a34 100644 --- a/Neuron/Sections/Wallet/Home/WalletPresenter.swift +++ b/Neuron/Sections/Wallet/Home/WalletPresenter.swift @@ -47,7 +47,6 @@ class WalletPresenter { private var appModelObserver: NotificationToken? private var selectTokenListObserver: NotificationToken? private var nativeTokenListObserver: NotificationToken? - private var tokenListTimestamp: TimeInterval = 0.0 init() { @@ -179,8 +178,8 @@ extension WalletPresenter { } }) DispatchQueue.main.async { - guard timestamp == self.tokenListTimestamp else { return } self.refreshing = false + guard timestamp == self.tokenListTimestamp else { return } self.delegate?.walletPresenterEndedRefresh(presenter: self) self.delegate?.walletPresenter(presenter: self, didRefreshTotalAmount: amount) } @@ -189,6 +188,7 @@ extension WalletPresenter { @objc func refreshPrice() { self.currency = LocalCurrencyService.shared.getLocalCurrencySelect() + refreshing = true delegate?.walletPresenterBeganRefresh(presenter: self) let tokens = self.tokens let currency = self.currency @@ -205,9 +205,9 @@ extension WalletPresenter { } } DispatchQueue.main.async { + self.refreshing = false guard currency?.short == self.currency.short else { return } guard timestamp == self.tokenListTimestamp else { return } - self.refreshing = false self.delegate?.walletPresenterEndedRefresh(presenter: self) self.delegate?.walletPresenter(presenter: self, didRefreshTotalAmount: amount) } diff --git a/Neuron/Sections/Wallet/Home/WalletViewController.swift b/Neuron/Sections/Wallet/Home/WalletViewController.swift index 23103c39..e3635076 100644 --- a/Neuron/Sections/Wallet/Home/WalletViewController.swift +++ b/Neuron/Sections/Wallet/Home/WalletViewController.swift @@ -142,7 +142,12 @@ extension WalletViewController: UITableViewDataSource, UITableViewDelegate { let token = presenter.tokens[indexPath.row] let cell: TokenTableViewCell if token.balance == nil { - cell = tableView.dequeueReusableCell(withIdentifier: "TokenTableViewCell_Loading") as! TokenTableViewCell + if presenter.refreshing { + cell = tableView.dequeueReusableCell(withIdentifier: "TokenTableViewCell_Loading") as! TokenTableViewCell + } else { + cell = tableView.dequeueReusableCell(withIdentifier: "TokenTableViewCell_LoadFail") as! TokenTableViewCell + } + } else if token.price == nil || token.balance == 0.0 { cell = tableView.dequeueReusableCell(withIdentifier: "TokenTableViewCell_NoPrice") as! TokenTableViewCell } else { @@ -177,14 +182,17 @@ extension WalletViewController: UITableViewDataSource, UITableViewDelegate { extension WalletViewController { func beganRefreshButtonAnimation() { + guard (refreshButton.layer.animationKeys()?.count ?? 0) <= 0 else { return } UIView.beginAnimations("refresh", context: nil) UIView.setAnimationDuration(0.4) UIView.setAnimationRepeatCount(Float(Int.max)) UIView.setAnimationCurve(.linear) refreshButton.transform = refreshButton.transform.rotated(by: CGFloat(Double.pi)) UIView.commitAnimations() + tableView.reloadData() } func endRefreshButtonAnimation() { refreshButton.layer.removeAllAnimations() + tableView.reloadData() } } From d22197dc8fa4780fb47e62b6a21b81b62c9f7483 Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 29 Nov 2018 14:54:33 +0800 Subject: [PATCH 269/315] Modify decimal format --- Neuron/Extensions/Foundation/Double+Extension.swift | 8 ++------ .../Transaction/SendTransactionViewController.swift | 2 +- .../Transaction/TransactionHistoryViewController.swift | 2 +- .../Wallet/TableViewCell/TokenTableViewCell.swift | 2 +- .../Extensions/Foundation/DoubleExtensionTests.swift | 8 ++++++++ 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Neuron/Extensions/Foundation/Double+Extension.swift b/Neuron/Extensions/Foundation/Double+Extension.swift index b985342a..374123e2 100644 --- a/Neuron/Extensions/Foundation/Double+Extension.swift +++ b/Neuron/Extensions/Foundation/Double+Extension.swift @@ -19,8 +19,8 @@ extension Double { let formatter = NumberFormatter() formatter.minimumIntegerDigits = 1 formatter.minimumFractionDigits = 0 - formatter.maximumFractionDigits = 6 - formatter.roundingMode = .halfUp + formatter.maximumFractionDigits = 8 + formatter.roundingMode = .floor return formatter }() @@ -43,8 +43,4 @@ extension Double { return 0 } } - - func toAmountText() -> String { - return self == Double(Int(self)) ? String(Int(self)) : String(self) - } } diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index d3630b64..70e3b029 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -170,7 +170,7 @@ class SendTransactionViewController: UITableViewController, TransactonSender { walletIconView.image = UIImage(data: wallet.iconData) walletNameLabel.text = wallet.name walletAddressLabel.text = wallet.address - tokenBalanceButton.setTitle("\(token.tokenBalance.toAmountText()) \(token.symbol)", for: .normal) + tokenBalanceButton.setTitle("\(token.tokenBalance.decimal) \(token.symbol)", for: .normal) addressTextField.text = paramBuilder.to tokenLabel.text = token.symbol diff --git a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift index 39e5c183..1fc6a7d6 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift @@ -214,7 +214,7 @@ class TransactionHistoryTableViewCell: UITableViewCell { dateLabel.text = dateformatter.string(from: transaction.date) let walletAddress = AppModel.current.currentWallet!.address - let amount = Web3Utils.formatToPrecision(transaction.value, numberDecimals: transaction.token.decimals)! + let amount = Double.fromAmount(transaction.value, decimals: transaction.token.decimals).decimal if transaction.from.lowercased() == walletAddress.lowercased() { addressLabel.text = transaction.from numberLabel.text = "-\(amount)" diff --git a/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift b/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift index e634fd00..3a09a12c 100644 --- a/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift +++ b/Neuron/Sections/Wallet/TableViewCell/TokenTableViewCell.swift @@ -22,7 +22,7 @@ class TokenTableViewCell: UITableViewCell { symbolLabel.text = token.symbol symbolWidthConstraint.constant = symbolLabel.textRect(forBounds: CGRect(x: 0, y: 0, width: 150, height: 20), limitedToNumberOfLines: 1).size.width if let balance = token.balance { - balanceLabel.text = balance.toAmountText() + balanceLabel.text = balance.decimal if balance > 0 { if let price = token.price { let amount = price * balance diff --git a/NeuronTests/Extensions/Foundation/DoubleExtensionTests.swift b/NeuronTests/Extensions/Foundation/DoubleExtensionTests.swift index 3897e8b4..7d959553 100644 --- a/NeuronTests/Extensions/Foundation/DoubleExtensionTests.swift +++ b/NeuronTests/Extensions/Foundation/DoubleExtensionTests.swift @@ -31,4 +31,12 @@ class DoubleExtensionTests: XCTestCase { 20.5.gweiToWei() ) } + + func testDecimalFormat() { + XCTAssertEqual(1.12345678.decimal, "1.12345678") + XCTAssertEqual(1.1234567899.decimal, "1.12345678") + XCTAssertEqual(1.123456784.decimal, "1.12345678") + XCTAssertEqual(1.1234.decimal, "1.1234") + XCTAssertEqual(4.decimal, "4") + } } From 1ed8415086f730a888a5140f121f844051a4f22e Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 29 Nov 2018 17:30:46 +0800 Subject: [PATCH 270/315] ETH balance loader --- Neuron.xcodeproj/project.pbxproj | 8 ++-- Neuron/Models/Token.swift | 14 ++---- .../Services/Ethereum/ERC20TokenService.swift | 26 ----------- .../Ethereum/EthNativeTokenService.swift | 43 ------------------- .../Ethereum/EthereumBalanceLoader.swift | 34 +++++++++++++++ 5 files changed, 42 insertions(+), 83 deletions(-) delete mode 100644 Neuron/Services/Ethereum/EthNativeTokenService.swift create mode 100644 Neuron/Services/Ethereum/EthereumBalanceLoader.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index d72c89a1..43708b73 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -69,6 +69,7 @@ 6E876845218031670032EBCE /* UInt+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E876844218031670032EBCE /* UInt+Extension.swift */; }; 6E8962032194575B00D2C0AD /* DoubleExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8962022194575B00D2C0AD /* DoubleExtensionTests.swift */; }; 6E8AFB0D21A7E7B800F81DC6 /* TransactionGasCostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8AFB0C21A7E7B800F81DC6 /* TransactionGasCostViewController.swift */; }; + 6E8BFC3121AFDBB5004752CC /* EthereumBalanceLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8BFC3021AFDBB5004752CC /* EthereumBalanceLoader.swift */; }; 6E8D77F021A5264F00EA7674 /* WalletQRCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8D77EF21A5264F00EA7674 /* WalletQRCodeViewController.swift */; }; 6E8D77F221A54B8100EA7674 /* TransactionSwitchTokenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8D77F121A54B8100EA7674 /* TransactionSwitchTokenViewController.swift */; }; 6E90D06921704C1D00B98363 /* SensorsAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E90D06821704C1D00B98363 /* SensorsAnalytics.swift */; }; @@ -151,7 +152,6 @@ 898A1A2F20B8044900ECB465 /* TradeTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898A1A2D20B8044900ECB465 /* TradeTableViewCell.swift */; }; 898A1A3020B8044900ECB465 /* TradeTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 898A1A2E20B8044900ECB465 /* TradeTableViewCell.xib */; }; 898CA98320EA0F210059ECA3 /* TokenModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898CA98220EA0F210059ECA3 /* TokenModel.swift */; }; - 898CA98820EA23940059ECA3 /* EthNativeTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 898CA98720EA23940059ECA3 /* EthNativeTokenService.swift */; }; 898CA98A20EA2E770059ECA3 /* tokens-eth.json in Resources */ = {isa = PBXBuildFile; fileRef = 898CA98920EA2E760059ECA3 /* tokens-eth.json */; }; 899AD19921100F2B00A8AC09 /* NervosNativeTokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 899AD19821100F2B00A8AC09 /* NervosNativeTokenService.swift */; }; 899AD19B211011F300A8AC09 /* AppChainNetwork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 899AD19A211011F300A8AC09 /* AppChainNetwork.swift */; }; @@ -283,6 +283,7 @@ 6E876844218031670032EBCE /* UInt+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt+Extension.swift"; sourceTree = ""; }; 6E8962022194575B00D2C0AD /* DoubleExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoubleExtensionTests.swift; sourceTree = ""; }; 6E8AFB0C21A7E7B800F81DC6 /* TransactionGasCostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionGasCostViewController.swift; sourceTree = ""; }; + 6E8BFC3021AFDBB5004752CC /* EthereumBalanceLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumBalanceLoader.swift; sourceTree = ""; }; 6E8D77EF21A5264F00EA7674 /* WalletQRCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletQRCodeViewController.swift; sourceTree = ""; }; 6E8D77F121A54B8100EA7674 /* TransactionSwitchTokenViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionSwitchTokenViewController.swift; sourceTree = ""; }; 6E90D06821704C1D00B98363 /* SensorsAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorsAnalytics.swift; sourceTree = ""; }; @@ -373,7 +374,6 @@ 898A1A2D20B8044900ECB465 /* TradeTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TradeTableViewCell.swift; sourceTree = ""; }; 898A1A2E20B8044900ECB465 /* TradeTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TradeTableViewCell.xib; sourceTree = ""; }; 898CA98220EA0F210059ECA3 /* TokenModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenModel.swift; sourceTree = ""; }; - 898CA98720EA23940059ECA3 /* EthNativeTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthNativeTokenService.swift; sourceTree = ""; }; 898CA98920EA2E760059ECA3 /* tokens-eth.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "tokens-eth.json"; sourceTree = ""; }; 899AD19821100F2B00A8AC09 /* NervosNativeTokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NervosNativeTokenService.swift; sourceTree = ""; }; 899AD19A211011F300A8AC09 /* AppChainNetwork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppChainNetwork.swift; sourceTree = ""; }; @@ -523,7 +523,7 @@ children = ( 89BEBE6A20EF82B6007D2705 /* EthereumNetwork.swift */, 1A69465B21916BE5000093A2 /* EthereumTxSender.swift */, - 898CA98720EA23940059ECA3 /* EthNativeTokenService.swift */, + 6E8BFC3021AFDBB5004752CC /* EthereumBalanceLoader.swift */, 89FE21D720EC7E1800A09302 /* ERC20TokenService.swift */, 89FE21D920EDB10900A09302 /* CustomERC20TokenService.swift */, 89733043217ED041001B8D93 /* ETHSignMessageService.swift */, @@ -1472,6 +1472,7 @@ 1A2B226921A7907500556744 /* UIColor+Extension.swift in Sources */, 6E876845218031670032EBCE /* UInt+Extension.swift in Sources */, 6E90D06921704C1D00B98363 /* SensorsAnalytics.swift in Sources */, + 6E8BFC3121AFDBB5004752CC /* EthereumBalanceLoader.swift in Sources */, 89D84CE92147D926006B0287 /* NFTDetailViewController.swift in Sources */, 898A1A0320B5717A00ECB465 /* ManageAssetViewController.swift in Sources */, 6E3B2B73219EDB870095257D /* AppChain+TransactionStatus.swift in Sources */, @@ -1503,7 +1504,6 @@ 89AB35CA213D113E00A2BF48 /* WalletTableViewCell.swift in Sources */, 89422FE621A7F92300DD8744 /* DAppModel.swift in Sources */, 8928C24920AE91E600C3103E /* DappViewController.swift in Sources */, - 898CA98820EA23940059ECA3 /* EthNativeTokenService.swift in Sources */, 1A6007A021A29C2C00C7B712 /* SuccessPageItem.swift in Sources */, 6E867E09216C862900BD6FE5 /* NoScreenshot.swift in Sources */, 89C614592140FE6600DC3DF4 /* CurrencyTableViewCell.swift in Sources */, diff --git a/Neuron/Models/Token.swift b/Neuron/Models/Token.swift index 5326c75d..a7fe3453 100644 --- a/Neuron/Models/Token.swift +++ b/Neuron/Models/Token.swift @@ -73,20 +73,14 @@ class Token { refreshBalanceSignal?.enter() let balance: BigUInt switch type { - case .ether: - balance = try EthereumNetwork().getWeb3().eth.getBalance(address: EthereumAddress(walletAddress)!) case .appChain, .appChainErc20: balance = try AppChainNetwork.appChain(url: URL(string: chainHosts)).rpc.getBalance(address: walletAddress) + case .ether: + balance = try EthereumBalanceLoader(web3: EthereumNetwork().getWeb3(), address: walletAddress).getBalance() case .erc20: - let contractAddress = EthereumAddress(address)! - let walletAddress = EthereumAddress(self.walletAddress)! - let contract = EthereumNetwork().getWeb3().contract(Web3.Utils.erc20ABI, at: contractAddress, abiVersion: 2)! - let result = try contract.method("balanceOf", parameters: [walletAddress as AnyObject])?.call() - balance = result?["0"] as! BigUInt + balance = try EthereumBalanceLoader(web3: EthereumNetwork().getWeb3(), address: walletAddress).getTokenBalance(address: address) } -// let balanceText = Web3Utils.formatToEthereumUnits(balance, toUnits: .eth, decimals: 8) ?? "0" - let balanceText = Web3Utils.formatToPrecision(balance, numberDecimals: decimals, formattingDecimals: 6) ?? "0" - self.balance = Double(balanceText) + self.balance = Double.fromAmount(balance, decimals: decimals) refreshBalanceSignal?.leave() refreshBalanceSignal = nil return self.balance diff --git a/Neuron/Services/Ethereum/ERC20TokenService.swift b/Neuron/Services/Ethereum/ERC20TokenService.swift index e5e2554b..eef9ce39 100644 --- a/Neuron/Services/Ethereum/ERC20TokenService.swift +++ b/Neuron/Services/Ethereum/ERC20TokenService.swift @@ -12,32 +12,6 @@ import EthereumAddress import struct BigInt.BigUInt struct ERC20TokenService { - /// getBalance - /// - /// - Parameters: - /// - walletAddress: wallet public key - /// - contractAddress: token address - /// - completion: balance result - static func getERC20TokenBalance(walletAddress: String, contractAddress: String, completion: @escaping (EthServiceResult) -> Void) { - let web3 = EthereumNetwork().getWeb3() - let contractETHAddress = EthereumAddress(contractAddress)! - let coldWalletAddress = EthereumAddress(walletAddress) - let contract = web3.contract(Web3.Utils.erc20ABI, at: contractETHAddress, abiVersion: 2)! - - DispatchQueue.global().async { - do { - let result = try contract.method("balanceOf", parameters: [coldWalletAddress as AnyObject])!.call(transactionOptions: nil) - DispatchQueue.main.async { - completion(EthServiceResult.success(result["0"] as! BigUInt)) - } - } catch let error { - DispatchQueue.main.async { - completion(EthServiceResult.error(error)) - } - } - } - } - /// search ETC20Token data for contractaddress /// /// - Parameters: diff --git a/Neuron/Services/Ethereum/EthNativeTokenService.swift b/Neuron/Services/Ethereum/EthNativeTokenService.swift deleted file mode 100644 index 306094dc..00000000 --- a/Neuron/Services/Ethereum/EthNativeTokenService.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// EthNativeTokenService.swift -// Neuron -// -// Created by XiaoLu on 2018/7/2. -// Copyright © 2018年 cryptape. All rights reserved. -// - -import Foundation -import BigInt -import Web3swift -import EthereumAddress - -// TODO: create balanceLoader -struct EthNativeTokenService { - /// get balance - /// - /// - Parameters: - /// - walletAddress: wallet address - /// - completion: EthServiceResult - static func getEthNativeTokenBalance(walletAddress: String, completion: @escaping (EthServiceResult) -> Void) { - let address = EthereumAddress(walletAddress)! - let web3Main = EthereumNetwork().getWeb3() - DispatchQueue.global().async { - do { - let balance = try web3Main.eth.getBalance(address: address) - DispatchQueue.main.async { - completion(EthServiceResult.success(self.formatBalanceValue(value: balance))) - } - } catch let error { - DispatchQueue.main.async { - completion(EthServiceResult.error(error)) - } - } - } - } - - private static func formatBalanceValue(value: BigUInt) -> String { - let format = Web3.Utils.formatToPrecision(value, formattingDecimals: 8, fallbackToScientific: false)! - let finalValue = Double(format)! - return finalValue.trailingZerosTrimmed - } -} diff --git a/Neuron/Services/Ethereum/EthereumBalanceLoader.swift b/Neuron/Services/Ethereum/EthereumBalanceLoader.swift new file mode 100644 index 00000000..fe7bee03 --- /dev/null +++ b/Neuron/Services/Ethereum/EthereumBalanceLoader.swift @@ -0,0 +1,34 @@ +// +// EthereumBalanceLoader.swift +// Neuron +// +// Created by 晨风 on 2018/11/29. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import BigInt +import Web3swift +import EthereumAddress + +class EthereumBalanceLoader { + private let web3: web3 + private let walletAddress: String + + init(web3: web3, address: String) { + self.web3 = web3 + walletAddress = address + } + + func getBalance() throws -> BigUInt { + return try web3.eth.getBalance(address: EthereumAddress(walletAddress)!) + } + + func getTokenBalance(address: String) throws -> BigUInt { + let contractAddress = EthereumAddress(address)! + let walletAddress = EthereumAddress(self.walletAddress)! + let contract = web3.contract(Web3.Utils.erc20ABI, at: contractAddress, abiVersion: 2)! + let result = try contract.method("balanceOf", parameters: [walletAddress as AnyObject])?.call() + return result?["0"] as! BigUInt + } +} From 8f843baeb46d133ee6432759a20bb85fdf14199c Mon Sep 17 00:00:00 2001 From: cezres Date: Thu, 29 Nov 2018 18:03:39 +0800 Subject: [PATCH 271/315] Remove `AppChainServiceResult` --- .../AppChain/NervosNativeTokenService.swift | 56 ++++--------------- Neuron/Services/Common/ServiceErrors.swift | 6 -- 2 files changed, 12 insertions(+), 50 deletions(-) diff --git a/Neuron/Services/AppChain/NervosNativeTokenService.swift b/Neuron/Services/AppChain/NervosNativeTokenService.swift index 2ddc8621..2aab6293 100644 --- a/Neuron/Services/AppChain/NervosNativeTokenService.swift +++ b/Neuron/Services/AppChain/NervosNativeTokenService.swift @@ -11,50 +11,18 @@ import AppChain import BigInt struct NervosNativeTokenService { - static func getNervosNativeTokenMsg(blockNumber: String = "latest", completion: @escaping (AppChainServiceResult) -> Void) { + static func getNervosNativeTokenMsg(blockNumber: String = "latest") throws -> TokenModel { let appChain = AppChainNetwork.appChain() - DispatchQueue.global().async { - do { - let metaData = try appChain.rpc.getMetaData(blockNumber: blockNumber) - DispatchQueue.main.async { - let tokenModel = TokenModel() - tokenModel.address = "" - tokenModel.chainId = metaData.chainId.description - tokenModel.chainName = metaData.chainName - tokenModel.iconUrl = metaData.tokenAvatar - tokenModel.isNativeToken = true - tokenModel.name = metaData.tokenName - tokenModel.symbol = metaData.tokenSymbol - tokenModel.decimals = NativeDecimals.nativeTokenDecimals - completion(AppChainServiceResult.success(tokenModel)) - } - } catch let error { - DispatchQueue.main.async { - completion(AppChainServiceResult.error(error)) - } - } - } - } - - static func getNervosNativeTokenBalance(walletAddress: String, completion: @escaping (AppChainServiceResult) -> Void) { - let appChain = AppChainNetwork.appChain() - DispatchQueue.global().async { - do { - let balance = try appChain.rpc.getBalance(address: Address(walletAddress)!) - DispatchQueue.main.async { - completion(AppChainServiceResult.success(self.formatBalanceValue(value: balance))) - } - } catch let error { - DispatchQueue.main.async { - completion(AppChainServiceResult.error(error)) - } - } - } - } - - private static func formatBalanceValue(value: BigUInt) -> String { - let format = Web3Utils.formatToPrecision(value, formattingDecimals: 8, fallbackToScientific: false)! - let finalValue = Double(format)! - return finalValue.trailingZerosTrimmed + let metaData = try appChain.rpc.getMetaData(blockNumber: blockNumber) + let tokenModel = TokenModel() + tokenModel.address = "" + tokenModel.chainId = metaData.chainId.description + tokenModel.chainName = metaData.chainName + tokenModel.iconUrl = metaData.tokenAvatar + tokenModel.isNativeToken = true + tokenModel.name = metaData.tokenName + tokenModel.symbol = metaData.tokenSymbol + tokenModel.decimals = NativeDecimals.nativeTokenDecimals + return tokenModel } } diff --git a/Neuron/Services/Common/ServiceErrors.swift b/Neuron/Services/Common/ServiceErrors.swift index 2695d2e0..e25c38c2 100644 --- a/Neuron/Services/Common/ServiceErrors.swift +++ b/Neuron/Services/Common/ServiceErrors.swift @@ -14,12 +14,6 @@ enum EthServiceResult { case error(Error) } -// TODO: Remove result type -enum AppChainServiceResult { - case success(T) - case error(Error) -} - enum CustomTokenError: String, LocalizedError { case wrongBalanceError case badNameError From 2a1fd92da868273dde5f223c26a6ed2949b9a20f Mon Sep 17 00:00:00 2001 From: LuFP Date: Thu, 29 Nov 2018 18:27:40 +0800 Subject: [PATCH 272/315] Modify DApp data type --- .../Foundation/String+Extension.swift | 8 +++ .../Sections/Dapp/Handler/DAppCommand.swift | 64 +++++++++++++++---- .../Dapp/WebView/BrowserViewController.swift | 31 +++++++++ .../Dapp/WebView/ContractController.swift | 45 ++++++------- Neuron/en.lproj/Localizable.strings | 8 +++ Neuron/zh-Hans.lproj/Localizable.strings | 7 ++ 6 files changed, 127 insertions(+), 36 deletions(-) diff --git a/Neuron/Extensions/Foundation/String+Extension.swift b/Neuron/Extensions/Foundation/String+Extension.swift index 3c0c2499..2ced6617 100644 --- a/Neuron/Extensions/Foundation/String+Extension.swift +++ b/Neuron/Extensions/Foundation/String+Extension.swift @@ -27,4 +27,12 @@ extension String { func localized(_ comment: String = "") -> String { return NSLocalizedString(self, comment: comment) } + + func formatBigUInt() -> BigUInt? { + if self.hasPrefix("0x") { + return BigUInt(self.removeHexPrefix(), radix: 16) + } else { + return BigUInt(self) + } + } } diff --git a/Neuron/Sections/Dapp/Handler/DAppCommand.swift b/Neuron/Sections/Dapp/Handler/DAppCommand.swift index 6c961651..bc8128cf 100644 --- a/Neuron/Sections/Dapp/Handler/DAppCommand.swift +++ b/Neuron/Sections/Dapp/Handler/DAppCommand.swift @@ -7,6 +7,7 @@ // import Foundation +import BigInt struct DAppCommonModel: Decodable { let name: Method @@ -44,13 +45,50 @@ struct DAppCommonModel: Decodable { struct AppChainObject: Decodable { let chainId: Int - let data: String - let nonce: Double - let quota: Double + let data: String? + let nonce: String? + var quota: String? let to: String - let validUntilBlock: Double - let value: String + let validUntilBlock: UInt64 + var value: String? let version: Int + + enum CodingKeys: String, CodingKey { + case chainId + case data + case value + case nonce + case quota + case to + case validUntilBlock + case version + } + + init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: CodingKeys.self) + chainId = try! values.decode(Int.self, forKey: .chainId) + data = try? values.decode(String.self, forKey: .data) + to = try! values.decode(String.self, forKey: .to) + version = try! values.decode(Int.self, forKey: .version) + validUntilBlock = try! values.decode(UInt64.self, forKey: .validUntilBlock) + nonce = try? values.decode(String.self, forKey: .nonce) + if let quota = try? values.decode(String.self, forKey: .quota) { + self.quota = quota + } + if let value = try? values.decode(String.self, forKey: .value) { + self.value = value + } + } + + static func formatValue(value: String) -> BigUInt? { + let finalValue: BigUInt? + if value.hasPrefix("0x") { + finalValue = BigUInt(value.removeHexPrefix(), radix: 16) + } else { + finalValue = BigUInt(value, radix: 10) + } + return finalValue + } } struct ETHObject: Decodable { @@ -59,8 +97,8 @@ struct ETHObject: Decodable { var data: String? var from: String? var to: String? - var gasLimit: Double? - var gasPrice: Double? + var gasLimit: String? + var gasPrice: String? enum CodingKeys: String, CodingKey { case chainId @@ -75,15 +113,17 @@ struct ETHObject: Decodable { init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) chainId = try? values.decode(Int.self, forKey: .chainId) - value = try? values.decode(String.self, forKey: .value) data = try? values.decode(String.self, forKey: .data) from = try? values.decode(String.self, forKey: .from) to = try? values.decode(String.self, forKey: .to) - gasPrice = try? values.decode(Double.self, forKey: .gasPrice) - if let gasLimit = try? values.decode(Double.self, forKey: .gasLimit) { + if let value = try? values.decode(String.self, forKey: .value) { + self.value = value + } + if let gasPrice = try? values.decode(String.self, forKey: .gasPrice) { + self.gasPrice = gasPrice + } + if let gasLimit = try? values.decode(String.self, forKey: .gasLimit) { self.gasLimit = gasLimit - } else if let gasLimitString = try? values.decode(String.self, forKey: .gasLimit) { - self.gasLimit = Double(gasLimitString) } } } diff --git a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift index 27d4076c..0c5a6d6d 100644 --- a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift @@ -255,6 +255,9 @@ extension BrowserViewController: UIScrollViewDelegate { extension BrowserViewController { private func pushTransaction(dappCommonModel: DAppCommonModel) { + if !identificateValueAndGas(dappModel: dappCommonModel) { + return + } let contractController = storyboard!.instantiateViewController(withIdentifier: "contractController") as! ContractController contractController.delegate = self contractController.requestAddress = webView.url!.absoluteString @@ -270,6 +273,34 @@ extension BrowserViewController { controller.dappCommonModel = dappCommonModel present(controller, animated: false) } + + func identificateValueAndGas(dappModel: DAppCommonModel) -> Bool { + if dappModel.chainType == "AppChain" { + if dappModel.appChain?.value?.formatBigUInt() == nil { + Toast.showToast(text: "DApp.SendTransactionError.emptyValue".localized()) + return false + } + if dappModel.appChain?.quota == nil || dappModel.appChain?.quota?.count == 0 { + Toast.showToast(text: "DApp.SendTransactionError.emptyQuota".localized()) + return false + } + return true + } else { + if dappModel.eth?.value?.formatBigUInt() == nil { + Toast.showToast(text: "DApp.SendTransactionError.emptyValue".localized()) + return false + } + if dappModel.eth?.gasLimit?.formatBigUInt() == nil { + Toast.showToast(text: "DApp.SendTransactionError.emptyGasLimit".localized()) + return false + } + if dappModel.eth?.gasPrice?.formatBigUInt() == nil { + Toast.showToast(text: "DApp.SendTransactionError.emptyGasPrice".localized()) + return false + } + return true + } + } } extension BrowserViewController: WKNavigationDelegate { diff --git a/Neuron/Sections/Dapp/WebView/ContractController.swift b/Neuron/Sections/Dapp/WebView/ContractController.swift index 9973dec5..e7eac299 100644 --- a/Neuron/Sections/Dapp/WebView/ContractController.swift +++ b/Neuron/Sections/Dapp/WebView/ContractController.swift @@ -107,19 +107,19 @@ class ContractController: UITableViewController, TransactonSender { dappNameLabel.text = dappName if dappCommonModel.chainType == "AppChain" { - let appChainQuota = BigUInt(dappCommonModel.appChain?.quota.trailingZerosTrimmed ?? "1000000")! + let appChainQuota = dappCommonModel.appChain?.quota!.formatBigUInt() ?? 0 chainType = .appChain toLabel.text = dappCommonModel.appChain?.to - value = formatScientValue(value: dappCommonModel.appChain?.value ?? "0") + value = formatScientValue(value: dappCommonModel.appChain?.value?.formatBigUInt() ?? 0) valueLabel.text = value gasLabel.text = getNervosTransactionCosted(with: appChainQuota) + tokenModel.symbol - totlePayLabel.text = getTotleValue(value: dappCommonModel.appChain?.value ?? "0", gas: appChainQuota) + tokenModel.symbol + totlePayLabel.text = getTotleValue(value: dappCommonModel.appChain?.value?.formatBigUInt() ?? 0, gas: appChainQuota) + tokenModel.symbol } else { chainType = .eth toLabel.text = dappCommonModel.eth?.to - value = formatScientValue(value: dappCommonModel.eth?.value ?? "0") + value = formatScientValue(value: dappCommonModel.eth?.value?.formatBigUInt() ?? "0") valueLabel.text = value - getETHGas(ethGasPirce: dappCommonModel.eth?.gasPrice?.trailingZerosTrimmed, ethGasLimit: dappCommonModel.eth?.gasLimit?.trailingZerosTrimmed) + getETHGas(ethGasPirce: dappCommonModel.eth?.gasPrice, ethGasLimit: dappCommonModel.eth?.gasLimit) } formatValueLabel(value: value) } @@ -150,20 +150,17 @@ class ContractController: UITableViewController, TransactonSender { return Web3Utils.formatToEthereumUnits(quotaInput, toUnits: .eth, decimals: 1, fallbackToScientific: true)! } - func formatScientValue(value: String) -> String { - let biguInt = BigUInt(atof(value)) - let format = Web3Utils.formatToEthereumUnits(biguInt, toUnits: .eth, decimals: 8, fallbackToScientific: false)! + func formatScientValue(value: BigUInt) -> String { + let format = Web3Utils.formatToEthereumUnits(value, toUnits: .eth, decimals: 8, fallbackToScientific: false)! let finalValue = Double(format)! return finalValue.trailingZerosTrimmed } // gas is equal to appChain's quota - func getTotleValue(value: String, gas: BigUInt) -> String { - let biguInt = BigUInt(atof(value)) - let finalValue = biguInt + gas + func getTotleValue(value: BigUInt, gas: BigUInt) -> String { + let finalValue = value + gas let formatValue = Web3Utils.formatToEthereumUnits(finalValue, toUnits: .eth, decimals: 8, fallbackToScientific: true)! - let finalCost = Double(formatValue)! - return finalCost.trailingZerosTrimmed + return Double(formatValue)!.trailingZerosTrimmed } func getETHGas(ethGasPirce: String?, ethGasLimit: String?) { @@ -171,36 +168,36 @@ class ContractController: UITableViewController, TransactonSender { DispatchQueue.global().async { let web3 = EthereumNetwork().getWeb3() if ethGasPirce != nil { - self.gasPrice = BigUInt(ethGasPirce!)! + self.gasPrice = ethGasLimit!.formatBigUInt()! } else { do { let gasPrice = try web3.eth.getGasPrice() self.gasPrice = gasPrice } catch { - self.gasPrice = BigUInt(8) + self.gasPrice = BigUInt(0) } } if ethGasLimit != nil { - self.gasLimit = BigUInt(ethGasLimit!) ?? BigUInt(1000000) + self.gasLimit = ethGasLimit!.formatBigUInt()! } else { var options = TransactionOptions() options.gasLimit = .limited(self.gasLimit) options.from = EthereumAddress(self.dappCommonModel.eth?.from ?? "") - options.value = BigUInt(self.dappCommonModel.eth?.value ?? "0") + options.value = self.dappCommonModel.eth?.value?.formatBigUInt() let contract = web3.contract(Web3.Utils.coldWalletABI, at: EthereumAddress(self.dappCommonModel.eth?.to ?? ""))! if let estimatedGas = try? contract.method(transactionOptions: options)!.estimateGas(transactionOptions: nil) { self.gasLimit = estimatedGas } else { - self.gasLimit = BigUInt(1000000) + self.gasLimit = BigUInt(0) } } DispatchQueue.main.async { let gas = self.gasPrice * self.gasLimit self.ethereumGas = Web3Utils.formatToEthereumUnits(gas, toUnits: .eth, decimals: 8, fallbackToScientific: false) - self.gasLabel.text = Double(self.ethereumGas!)!.trailingZerosTrimmed + self.tokenModel.symbol + self.gasLabel.text = Double(self.ethereumGas!)!.decimal + self.tokenModel.symbol let bigUIntValue = Web3Utils.parseToBigUInt(self.value, units: .eth)! - self.totlePayLabel.text = self.getTotleValue(value: bigUIntValue.description, gas: gas) + self.tokenModel.symbol + self.totlePayLabel.text = self.getTotleValue(value: bigUIntValue.fromQuota(), gas: gas) + self.tokenModel.symbol Toast.hideHUD() } } @@ -239,7 +236,7 @@ class ContractController: UITableViewController, TransactonSender { switch chainType { case .appChain: paramBuilder.gasLimit = 10000000 - paramBuilder.gasPrice = BigUInt(dappCommonModel.appChain?.quota.trailingZerosTrimmed ?? "") ?? 1000000 + paramBuilder.gasPrice = dappCommonModel.appChain?.quota?.formatBigUInt() ?? 1000000 paramBuilder.to = dappCommonModel.appChain?.to ?? "" paramBuilder.data = Data(hex: dappCommonModel.appChain?.data ?? "") case .eth: @@ -312,10 +309,10 @@ private extension ContractController { extension ContractController: AdvancedViewControllerDelegate { func getCustomGas(gasPrice: BigUInt, gas: BigUInt) { self.gasPrice = gasPrice - ethereumGas = Web3Utils.formatToEthereumUnits(gas, toUnits: .eth, decimals: 8, fallbackToScientific: true) ?? "0" + ethereumGas = Web3Utils.formatToEthereumUnits(gas, toUnits: .eth, decimals: 8, fallbackToScientific: true) let bigUIntValue = Web3Utils.parseToBigUInt(value, units: .eth)! - let totlePay = getTotleValue(value: bigUIntValue.description, gas: gas) + let totlePay = getTotleValue(value: bigUIntValue, gas: gas) totlePayLabel.text = totlePay + tokenModel.symbol - gasLabel.text = Double(ethereumGas ?? "")!.trailingZerosTrimmed + tokenModel.symbol + gasLabel.text = Double(ethereumGas ?? "")!.decimal + tokenModel.symbol } } diff --git a/Neuron/en.lproj/Localizable.strings b/Neuron/en.lproj/Localizable.strings index 757548f4..e2f2cd28 100644 --- a/Neuron/en.lproj/Localizable.strings +++ b/Neuron/en.lproj/Localizable.strings @@ -15,6 +15,14 @@ // --------------------------------------------------------- "DApp.Home" = "DApps"; +// DApp Transaction +// --------------------------------------------------------- +"DApp.SendTransactionError.emptyValue" = "Transaction's value format error"; +"DApp.SendTransactionError.emptyQuota" = "Transaction's quota format error"; +"DApp.SendTransactionError.emptyGasLimit" = "Transaction's gas limit format error"; +"DApp.SendTransactionError.emptyGasPrice" = "Transaction's gas price format error"; + + // Mnemonic Validator // --------------------------------------------------------- "MnemonicValidator.emptyMnemonic" = "Empty Mnemonic"; diff --git a/Neuron/zh-Hans.lproj/Localizable.strings b/Neuron/zh-Hans.lproj/Localizable.strings index c9ef788e..62d6e711 100644 --- a/Neuron/zh-Hans.lproj/Localizable.strings +++ b/Neuron/zh-Hans.lproj/Localizable.strings @@ -15,6 +15,13 @@ // --------------------------------------------------------- "DApp.Home" = "应用"; +// DApp Transaction +// --------------------------------------------------------- +"DApp.SendTransactionError.emptyValue" = "Transaction's value format error"; +"DApp.SendTransactionError.emptyQuota" = "Transaction's quota format error"; +"DApp.SendTransactionError.emptyGasLimit" = "Transaction's gas limit format error"; +"DApp.SendTransactionError.emptyGasPrice" = "Transaction's gas price format error"; + // Mnemonic Validator // --------------------------------------------------------- "MnemonicValidator.emptyMnemonic" = "助记词不能为空"; From e0516c2da3165c1fde69142a4f529ba6bd45e4cc Mon Sep 17 00:00:00 2001 From: LuFP Date: Thu, 29 Nov 2018 18:49:39 +0800 Subject: [PATCH 273/315] Reuse trailingZerosTrimmed --- Neuron/Sections/Dapp/WebView/ContractController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Neuron/Sections/Dapp/WebView/ContractController.swift b/Neuron/Sections/Dapp/WebView/ContractController.swift index e7eac299..d11c42f7 100644 --- a/Neuron/Sections/Dapp/WebView/ContractController.swift +++ b/Neuron/Sections/Dapp/WebView/ContractController.swift @@ -195,7 +195,7 @@ class ContractController: UITableViewController, TransactonSender { DispatchQueue.main.async { let gas = self.gasPrice * self.gasLimit self.ethereumGas = Web3Utils.formatToEthereumUnits(gas, toUnits: .eth, decimals: 8, fallbackToScientific: false) - self.gasLabel.text = Double(self.ethereumGas!)!.decimal + self.tokenModel.symbol + self.gasLabel.text = Double(self.ethereumGas!)!.trailingZerosTrimmed + self.tokenModel.symbol let bigUIntValue = Web3Utils.parseToBigUInt(self.value, units: .eth)! self.totlePayLabel.text = self.getTotleValue(value: bigUIntValue.fromQuota(), gas: gas) + self.tokenModel.symbol Toast.hideHUD() @@ -313,6 +313,6 @@ extension ContractController: AdvancedViewControllerDelegate { let bigUIntValue = Web3Utils.parseToBigUInt(value, units: .eth)! let totlePay = getTotleValue(value: bigUIntValue, gas: gas) totlePayLabel.text = totlePay + tokenModel.symbol - gasLabel.text = Double(ethereumGas ?? "")!.decimal + tokenModel.symbol + gasLabel.text = Double(ethereumGas ?? "")!.trailingZerosTrimmed + tokenModel.symbol } } From f76df2bdd9dd61dee5931f358395168adba37f65 Mon Sep 17 00:00:00 2001 From: LuFP Date: Thu, 29 Nov 2018 19:49:05 +0800 Subject: [PATCH 274/315] Fix NTF empty data text. --- Neuron/Sections/Dapp/Native/NFTViewController.swift | 1 + Neuron/Services/WalletManager/WalletManager.swift | 11 ++++++++--- Neuron/en.lproj/Localizable.strings | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Neuron/Sections/Dapp/Native/NFTViewController.swift b/Neuron/Sections/Dapp/Native/NFTViewController.swift index 0c8219da..82d0772a 100644 --- a/Neuron/Sections/Dapp/Native/NFTViewController.swift +++ b/Neuron/Sections/Dapp/Native/NFTViewController.swift @@ -33,6 +33,7 @@ class NFTViewController: UITableViewController, ErrorOverlayPresentable { self.dataArray = nftModel.assets ?? [] if self.dataArray.count == 0 { self.showBlankOverlay() + self.errorOverlaycontroller.messageLabel.text = "DApp.NFT.EmptyData".localized() } else { self.removeOverlay() } diff --git a/Neuron/Services/WalletManager/WalletManager.swift b/Neuron/Services/WalletManager/WalletManager.swift index f6550e4d..f40b2e31 100755 --- a/Neuron/Services/WalletManager/WalletManager.swift +++ b/Neuron/Services/WalletManager/WalletManager.swift @@ -78,10 +78,15 @@ extension WalletManager { do { try keystore.regenerate(oldPassword: password, newPassword: password) - } catch { - throw Error.invalidPassword + } catch let error { + guard let error = error as? AbstractKeystoreError else { throw Error.unknown } + switch error { + case .invalidPasswordError: + throw Error.invalidPassword + default: + throw Error.invalidKeystore + } } - return try add(keystore, address) } diff --git a/Neuron/en.lproj/Localizable.strings b/Neuron/en.lproj/Localizable.strings index 757548f4..bb44acbc 100644 --- a/Neuron/en.lproj/Localizable.strings +++ b/Neuron/en.lproj/Localizable.strings @@ -14,6 +14,7 @@ // DApp // --------------------------------------------------------- "DApp.Home" = "DApps"; +"DApp.NFT.EmptyData" = "Empty Data"; // Mnemonic Validator // --------------------------------------------------------- From 00b9d303560d06ed287a4787ef094680e23dc09e Mon Sep 17 00:00:00 2001 From: LuFP Date: Thu, 29 Nov 2018 19:49:54 +0800 Subject: [PATCH 275/315] Fix repeat add ERC20 token --- Neuron/Sections/Wallet/Assets/AddAssetController.swift | 7 +++++-- Neuron/zh-Hans.lproj/Localizable.strings | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Neuron/Sections/Wallet/Assets/AddAssetController.swift b/Neuron/Sections/Wallet/Assets/AddAssetController.swift index 13f0a077..fd77c6cf 100644 --- a/Neuron/Sections/Wallet/Assets/AddAssetController.swift +++ b/Neuron/Sections/Wallet/Assets/AddAssetController.swift @@ -41,7 +41,7 @@ class AddAssetController: UIViewController, UITableViewDelegate, UITableViewData Toast.showToast(text: "Token信息不全,请核对合约地址是否正确") return } - if tokenArray.contains(where: { $0.address == tokenModel.address }) { + if tokenArray.contains(where: { $0.address.lowercased() == tokenModel.address.lowercased() }) { Toast.showToast(text: "不可重复添加") return } @@ -93,7 +93,7 @@ class AddAssetController: UIViewController, UITableViewDelegate, UITableViewData cell.rightTextField.text = tokenModel.symbol case 4: cell.isEdit = false - cell.rightTextField.text = tokenModel.decimals == 0 ? "" : String(tokenModel.decimals) + cell.rightTextField.text = tokenModel.decimals == 0 ? "0" : String(tokenModel.decimals) default: break } @@ -143,6 +143,9 @@ class AddAssetController: UIViewController, UITableViewDelegate, UITableViewData } func didGetERC20Token(token: String) { + tokenModel.name = "" + tokenModel.symbol = "" + tokenModel.decimals = 0 let appmodel = AppModel.current Toast.showHUD() ERC20TokenService.addERC20TokenToApp(contractAddress: token, walletAddress: (appmodel.currentWallet?.address)!) { (result) in diff --git a/Neuron/zh-Hans.lproj/Localizable.strings b/Neuron/zh-Hans.lproj/Localizable.strings index c9ef788e..aee05fd1 100644 --- a/Neuron/zh-Hans.lproj/Localizable.strings +++ b/Neuron/zh-Hans.lproj/Localizable.strings @@ -14,6 +14,7 @@ // DApp // --------------------------------------------------------- "DApp.Home" = "应用"; +"DApp.NFT.EmptyData" = "您还没有藏品数据"; // Mnemonic Validator // --------------------------------------------------------- From 9381390d7b7ec8f8f8aff5371e815bf4bbc3c36c Mon Sep 17 00:00:00 2001 From: LuFP Date: Thu, 29 Nov 2018 20:09:48 +0800 Subject: [PATCH 276/315] Update app version to V 0.7.0 --- Neuron/Info.plist | 4 ++-- .../Settings/AboutUsViewController.swift | 18 ++---------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/Neuron/Info.plist b/Neuron/Info.plist index 003cc693..5637acd0 100644 --- a/Neuron/Info.plist +++ b/Neuron/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.6.0 + 0.7.0 CFBundleVersion - 6 + 1 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/Neuron/Sections/Settings/AboutUsViewController.swift b/Neuron/Sections/Settings/AboutUsViewController.swift index d51617d2..83a5e61d 100644 --- a/Neuron/Sections/Settings/AboutUsViewController.swift +++ b/Neuron/Sections/Settings/AboutUsViewController.swift @@ -33,23 +33,9 @@ class AboutUsViewController: UITableViewController { func setVersionLabel() { let infoDictionary = Bundle.main.infoDictionary! - let appDisplayName = infoDictionary["CFBundleDisplayName"] let majorVersion = infoDictionary["CFBundleShortVersionString"] - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "yyMMdd" - let convertedDate = dateFormatter.string(from: compileDate) - versionLabel.text = appDisplayName as? String - versionLabel.text = "V \(String(describing: majorVersion!))" + ".\(convertedDate)" - } - - var compileDate: Date { - let bundleName = Bundle.main.infoDictionary!["CFBundleName"] as? String ?? "Info.plist" - if let infoPath = Bundle.main.path(forResource: bundleName, ofType: nil), - let infoAttr = try? FileManager.default.attributesOfItem(atPath: infoPath), - let infoDate = infoAttr[FileAttributeKey.creationDate] as? Date { - return infoDate - } - return Date() + let appBuild = infoDictionary["CFBundleVersion"] + versionLabel.text = "V \(String(describing: majorVersion!)).\(String(describing: appBuild!))" } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { From 304c808f5aa79d4d4124b61d37061519ea4bf41f Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 29 Nov 2018 22:04:59 +0900 Subject: [PATCH 277/315] Do not reset build number when bumping major version --- Neuron/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neuron/Info.plist b/Neuron/Info.plist index 5637acd0..b8cd5f27 100644 --- a/Neuron/Info.plist +++ b/Neuron/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 0.7.0 CFBundleVersion - 1 + 7 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS From f49d68ace7b745d6224c0921dfa845b9ae21c80b Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 29 Nov 2018 22:05:30 +0900 Subject: [PATCH 278/315] Show version detail as Version [version] (build) --- .../Sections/Settings/AboutUsViewController.swift | 13 ++++++------- Neuron/en.lproj/Localizable.strings | 4 ++++ Neuron/zh-Hans.lproj/Localizable.strings | 4 ++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Neuron/Sections/Settings/AboutUsViewController.swift b/Neuron/Sections/Settings/AboutUsViewController.swift index 83a5e61d..484bb4d2 100644 --- a/Neuron/Sections/Settings/AboutUsViewController.swift +++ b/Neuron/Sections/Settings/AboutUsViewController.swift @@ -10,11 +10,11 @@ import UIKit import SafariServices class AboutUsViewController: UITableViewController { - @IBOutlet weak var versionLabel: UILabel! + @IBOutlet private weak var versionLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() - title = "关于我们" + title = "Settings.About.AboutUs".localized() setVersionLabel() } @@ -31,11 +31,10 @@ class AboutUsViewController: UITableViewController { "https://github.com/cryptape/cita" ] - func setVersionLabel() { - let infoDictionary = Bundle.main.infoDictionary! - let majorVersion = infoDictionary["CFBundleShortVersionString"] - let appBuild = infoDictionary["CFBundleVersion"] - versionLabel.text = "V \(String(describing: majorVersion!)).\(String(describing: appBuild!))" + private func setVersionLabel() { + let majorVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String + let build = Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String + versionLabel.text = "Version \(majorVersion) (\(build))" } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { diff --git a/Neuron/en.lproj/Localizable.strings b/Neuron/en.lproj/Localizable.strings index 757548f4..63a71a4c 100644 --- a/Neuron/en.lproj/Localizable.strings +++ b/Neuron/en.lproj/Localizable.strings @@ -15,6 +15,10 @@ // --------------------------------------------------------- "DApp.Home" = "DApps"; +// Settings +// --------------------------------------------------------- +"Settings.About.AboutUs" = "About us"; + // Mnemonic Validator // --------------------------------------------------------- "MnemonicValidator.emptyMnemonic" = "Empty Mnemonic"; diff --git a/Neuron/zh-Hans.lproj/Localizable.strings b/Neuron/zh-Hans.lproj/Localizable.strings index c9ef788e..5d3e7cdc 100644 --- a/Neuron/zh-Hans.lproj/Localizable.strings +++ b/Neuron/zh-Hans.lproj/Localizable.strings @@ -15,6 +15,10 @@ // --------------------------------------------------------- "DApp.Home" = "应用"; +// Settings +// --------------------------------------------------------- +"Settings.About.AboutUs" = "关于我们"; + // Mnemonic Validator // --------------------------------------------------------- "MnemonicValidator.emptyMnemonic" = "助记词不能为空"; From 1920c1ddc2f8c0500fc070523835eca8419d31cd Mon Sep 17 00:00:00 2001 From: James Chen Date: Thu, 29 Nov 2018 22:14:22 +0900 Subject: [PATCH 279/315] Rename String.formatBigUInt to String.toBigUInt --- .../Extensions/Foundation/String+Extension.swift | 12 ++++++------ .../Dapp/WebView/BrowserViewController.swift | 8 ++++---- .../Dapp/WebView/ContractController.swift | 16 ++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Neuron/Extensions/Foundation/String+Extension.swift b/Neuron/Extensions/Foundation/String+Extension.swift index 2ced6617..50cc37ee 100644 --- a/Neuron/Extensions/Foundation/String+Extension.swift +++ b/Neuron/Extensions/Foundation/String+Extension.swift @@ -11,14 +11,14 @@ import BigInt extension String { func removeHexPrefix() -> String { - if self.hasPrefix("0x") { - return String(self.dropFirst(2)) + if hasPrefix("0x") { + return String(dropFirst(2)) } return self } func addHexPrefix() -> String { - if self.hasPrefix("0x") { + if hasPrefix("0x") { return self } return "0x" + self @@ -28,9 +28,9 @@ extension String { return NSLocalizedString(self, comment: comment) } - func formatBigUInt() -> BigUInt? { - if self.hasPrefix("0x") { - return BigUInt(self.removeHexPrefix(), radix: 16) + func toBigUInt() -> BigUInt? { + if hasPrefix("0x") { + return BigUInt(removeHexPrefix(), radix: 16) } else { return BigUInt(self) } diff --git a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift index 0c5a6d6d..166d1a2e 100644 --- a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift @@ -276,7 +276,7 @@ extension BrowserViewController { func identificateValueAndGas(dappModel: DAppCommonModel) -> Bool { if dappModel.chainType == "AppChain" { - if dappModel.appChain?.value?.formatBigUInt() == nil { + if dappModel.appChain?.value?.toBigUInt() == nil { Toast.showToast(text: "DApp.SendTransactionError.emptyValue".localized()) return false } @@ -286,15 +286,15 @@ extension BrowserViewController { } return true } else { - if dappModel.eth?.value?.formatBigUInt() == nil { + if dappModel.eth?.value?.toBigUInt() == nil { Toast.showToast(text: "DApp.SendTransactionError.emptyValue".localized()) return false } - if dappModel.eth?.gasLimit?.formatBigUInt() == nil { + if dappModel.eth?.gasLimit?.toBigUInt() == nil { Toast.showToast(text: "DApp.SendTransactionError.emptyGasLimit".localized()) return false } - if dappModel.eth?.gasPrice?.formatBigUInt() == nil { + if dappModel.eth?.gasPrice?.toBigUInt() == nil { Toast.showToast(text: "DApp.SendTransactionError.emptyGasPrice".localized()) return false } diff --git a/Neuron/Sections/Dapp/WebView/ContractController.swift b/Neuron/Sections/Dapp/WebView/ContractController.swift index d11c42f7..5d019cff 100644 --- a/Neuron/Sections/Dapp/WebView/ContractController.swift +++ b/Neuron/Sections/Dapp/WebView/ContractController.swift @@ -107,17 +107,17 @@ class ContractController: UITableViewController, TransactonSender { dappNameLabel.text = dappName if dappCommonModel.chainType == "AppChain" { - let appChainQuota = dappCommonModel.appChain?.quota!.formatBigUInt() ?? 0 + let appChainQuota = dappCommonModel.appChain?.quota!.toBigUInt() ?? 0 chainType = .appChain toLabel.text = dappCommonModel.appChain?.to - value = formatScientValue(value: dappCommonModel.appChain?.value?.formatBigUInt() ?? 0) + value = formatScientValue(value: dappCommonModel.appChain?.value?.toBigUInt() ?? 0) valueLabel.text = value gasLabel.text = getNervosTransactionCosted(with: appChainQuota) + tokenModel.symbol - totlePayLabel.text = getTotleValue(value: dappCommonModel.appChain?.value?.formatBigUInt() ?? 0, gas: appChainQuota) + tokenModel.symbol + totlePayLabel.text = getTotleValue(value: dappCommonModel.appChain?.value?.toBigUInt() ?? 0, gas: appChainQuota) + tokenModel.symbol } else { chainType = .eth toLabel.text = dappCommonModel.eth?.to - value = formatScientValue(value: dappCommonModel.eth?.value?.formatBigUInt() ?? "0") + value = formatScientValue(value: dappCommonModel.eth?.value?.toBigUInt() ?? "0") valueLabel.text = value getETHGas(ethGasPirce: dappCommonModel.eth?.gasPrice, ethGasLimit: dappCommonModel.eth?.gasLimit) } @@ -168,7 +168,7 @@ class ContractController: UITableViewController, TransactonSender { DispatchQueue.global().async { let web3 = EthereumNetwork().getWeb3() if ethGasPirce != nil { - self.gasPrice = ethGasLimit!.formatBigUInt()! + self.gasPrice = ethGasLimit!.toBigUInt()! } else { do { let gasPrice = try web3.eth.getGasPrice() @@ -179,12 +179,12 @@ class ContractController: UITableViewController, TransactonSender { } if ethGasLimit != nil { - self.gasLimit = ethGasLimit!.formatBigUInt()! + self.gasLimit = ethGasLimit!.toBigUInt()! } else { var options = TransactionOptions() options.gasLimit = .limited(self.gasLimit) options.from = EthereumAddress(self.dappCommonModel.eth?.from ?? "") - options.value = self.dappCommonModel.eth?.value?.formatBigUInt() + options.value = self.dappCommonModel.eth?.value?.toBigUInt() let contract = web3.contract(Web3.Utils.coldWalletABI, at: EthereumAddress(self.dappCommonModel.eth?.to ?? ""))! if let estimatedGas = try? contract.method(transactionOptions: options)!.estimateGas(transactionOptions: nil) { self.gasLimit = estimatedGas @@ -236,7 +236,7 @@ class ContractController: UITableViewController, TransactonSender { switch chainType { case .appChain: paramBuilder.gasLimit = 10000000 - paramBuilder.gasPrice = dappCommonModel.appChain?.quota?.formatBigUInt() ?? 1000000 + paramBuilder.gasPrice = dappCommonModel.appChain?.quota?.toBigUInt() ?? 1000000 paramBuilder.to = dappCommonModel.appChain?.to ?? "" paramBuilder.data = Data(hex: dappCommonModel.appChain?.data ?? "") case .eth: From 560ef3355308e728bb04b94f171d0712a4b55e0c Mon Sep 17 00:00:00 2001 From: cezres Date: Fri, 30 Nov 2018 11:11:58 +0800 Subject: [PATCH 280/315] Add token price loader --- Neuron.xcodeproj/project.pbxproj | 12 +-- .../Transaction/TransactionParamBuilder.swift | 15 +-- .../Wallet/Home/WalletPresenter.swift | 15 +-- Neuron/Services/Common/CoinMarketCap.swift | 98 ------------------- Neuron/Services/Common/CurrencyService.swift | 69 ------------- Neuron/Services/Common/TokenPriceLoader.swift | 80 +++++++++++++++ Neuron/Services/Ethereum/TokenProfile.swift | 37 +++---- 7 files changed, 105 insertions(+), 221 deletions(-) delete mode 100644 Neuron/Services/Common/CoinMarketCap.swift delete mode 100644 Neuron/Services/Common/CurrencyService.swift create mode 100644 Neuron/Services/Common/TokenPriceLoader.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 43708b73..e32b2f7a 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -65,11 +65,11 @@ 6E867E0F216CA4EB00BD6FE5 /* SettingAuthenticationTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E867E0E216CA4EB00BD6FE5 /* SettingAuthenticationTableViewCell.swift */; }; 6E867E11216CA51600BD6FE5 /* SettingCurrencyTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E867E10216CA51600BD6FE5 /* SettingCurrencyTableViewCell.swift */; }; 6E876841218022F30032EBCE /* TokenProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E876840218022F30032EBCE /* TokenProfile.swift */; }; - 6E876843218023520032EBCE /* CoinMarketCap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E876842218023520032EBCE /* CoinMarketCap.swift */; }; 6E876845218031670032EBCE /* UInt+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E876844218031670032EBCE /* UInt+Extension.swift */; }; 6E8962032194575B00D2C0AD /* DoubleExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8962022194575B00D2C0AD /* DoubleExtensionTests.swift */; }; 6E8AFB0D21A7E7B800F81DC6 /* TransactionGasCostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8AFB0C21A7E7B800F81DC6 /* TransactionGasCostViewController.swift */; }; 6E8BFC3121AFDBB5004752CC /* EthereumBalanceLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8BFC3021AFDBB5004752CC /* EthereumBalanceLoader.swift */; }; + 6E8BFC3721B00948004752CC /* TokenPriceLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8BFC3621B00948004752CC /* TokenPriceLoader.swift */; }; 6E8D77F021A5264F00EA7674 /* WalletQRCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8D77EF21A5264F00EA7674 /* WalletQRCodeViewController.swift */; }; 6E8D77F221A54B8100EA7674 /* TransactionSwitchTokenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8D77F121A54B8100EA7674 /* TransactionSwitchTokenViewController.swift */; }; 6E90D06921704C1D00B98363 /* SensorsAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E90D06821704C1D00B98363 /* SensorsAnalytics.swift */; }; @@ -176,7 +176,6 @@ 89C6145E2141038300DC3DF4 /* LocalCurrencyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C6145D2141038300DC3DF4 /* LocalCurrencyService.swift */; }; 89C614602141421400DC3DF4 /* TransactionHistoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89C6145F2141421400DC3DF4 /* TransactionHistoryViewController.swift */; }; 89CA1FAC213F822B00669B2C /* tokens-list.json in Resources */ = {isa = PBXBuildFile; fileRef = 89CA1FAB213F822B00669B2C /* tokens-list.json */; }; - 89CA1FAE213F832800669B2C /* CurrencyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CA1FAD213F832800669B2C /* CurrencyService.swift */; }; 89CFA12621424CAB00635A54 /* AboutUsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89CFA12521424CAB00635A54 /* AboutUsViewController.swift */; }; 89D4AC2E20D59F270097E02B /* WalletManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D4AC2520D59F270097E02B /* WalletManager.swift */; }; 89D4AC3820D8DB610097E02B /* NotificationName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89D4AC3720D8DB610097E02B /* NotificationName.swift */; }; @@ -279,11 +278,11 @@ 6E867E0E216CA4EB00BD6FE5 /* SettingAuthenticationTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingAuthenticationTableViewCell.swift; sourceTree = ""; }; 6E867E10216CA51600BD6FE5 /* SettingCurrencyTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingCurrencyTableViewCell.swift; sourceTree = ""; }; 6E876840218022F30032EBCE /* TokenProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenProfile.swift; sourceTree = ""; }; - 6E876842218023520032EBCE /* CoinMarketCap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoinMarketCap.swift; sourceTree = ""; }; 6E876844218031670032EBCE /* UInt+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UInt+Extension.swift"; sourceTree = ""; }; 6E8962022194575B00D2C0AD /* DoubleExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoubleExtensionTests.swift; sourceTree = ""; }; 6E8AFB0C21A7E7B800F81DC6 /* TransactionGasCostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionGasCostViewController.swift; sourceTree = ""; }; 6E8BFC3021AFDBB5004752CC /* EthereumBalanceLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumBalanceLoader.swift; sourceTree = ""; }; + 6E8BFC3621B00948004752CC /* TokenPriceLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenPriceLoader.swift; sourceTree = ""; }; 6E8D77EF21A5264F00EA7674 /* WalletQRCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletQRCodeViewController.swift; sourceTree = ""; }; 6E8D77F121A54B8100EA7674 /* TransactionSwitchTokenViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionSwitchTokenViewController.swift; sourceTree = ""; }; 6E90D06821704C1D00B98363 /* SensorsAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorsAnalytics.swift; sourceTree = ""; }; @@ -398,7 +397,6 @@ 89C6145D2141038300DC3DF4 /* LocalCurrencyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalCurrencyService.swift; sourceTree = ""; }; 89C6145F2141421400DC3DF4 /* TransactionHistoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionHistoryViewController.swift; sourceTree = ""; }; 89CA1FAB213F822B00669B2C /* tokens-list.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "tokens-list.json"; sourceTree = ""; }; - 89CA1FAD213F832800669B2C /* CurrencyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyService.swift; sourceTree = ""; }; 89CFA12521424CAB00635A54 /* AboutUsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutUsViewController.swift; sourceTree = ""; }; 89D40EF520DCF1E00062C545 /* Neuron.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Neuron.entitlements; sourceTree = ""; }; 89D4AC2520D59F270097E02B /* WalletManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletManager.swift; sourceTree = ""; }; @@ -1043,12 +1041,11 @@ isa = PBXGroup; children = ( 89FE21DD20EDB70000A09302 /* ServiceErrors.swift */, - 6E876842218023520032EBCE /* CoinMarketCap.swift */, - 89CA1FAD213F832800669B2C /* CurrencyService.swift */, 89C6145D2141038300DC3DF4 /* LocalCurrencyService.swift */, 6E90D06821704C1D00B98363 /* SensorsAnalytics.swift */, 6E3B2B6A219D938F0095257D /* SentTransaction.swift */, 6E3B2B74219EDE200095257D /* TransactionStatusManager.swift */, + 6E8BFC3621B00948004752CC /* TokenPriceLoader.swift */, ); path = Common; sourceTree = ""; @@ -1413,7 +1410,6 @@ 6E3B2B6D219ED9240095257D /* Ethereum+TransactionDetails.swift in Sources */, 898A1A0F20B6539800ECB465 /* AddAssetTableViewCell.swift in Sources */, 6E9B947B21A4080300D67357 /* DesignableButton.swift in Sources */, - 89CA1FAE213F832800669B2C /* CurrencyService.swift in Sources */, 89A87C892173097000588674 /* WalletNameValidator.swift in Sources */, 89FE21D820EC7E1800A09302 /* ERC20TokenService.swift in Sources */, 6E3B2B6B219D938F0095257D /* SentTransaction.swift in Sources */, @@ -1442,7 +1438,6 @@ 6E867E0D216C92A900BD6FE5 /* OverlayPresentable.swift in Sources */, 89D84CE121475755006B0287 /* NFTService.swift in Sources */, 898A1A2A20B7F0FC00ECB465 /* TradeDetailsController.swift in Sources */, - 6E876843218023520032EBCE /* CoinMarketCap.swift in Sources */, 6E9F9D58219AAF78009ED8B2 /* NSObject+Extension.swift in Sources */, 89A196192175D9C800BD5FA4 /* DAppDataHandle.swift in Sources */, 89D84CEB2148C03B006B0287 /* NFTHeaderReusableView.swift in Sources */, @@ -1495,6 +1490,7 @@ 89C25C6D20BD421E00007EC1 /* ContractController.swift in Sources */, 6E3B2B6F219ED9340095257D /* Ethereum+TransactionStatus.swift in Sources */, 8935BA5F216F631A00C37263 /* ScriptMessageProxy.swift in Sources */, + 6E8BFC3721B00948004752CC /* TokenPriceLoader.swift in Sources */, 8928C25920AEB18100C3103E /* NibLoadable.swift in Sources */, 8951A84C2170A5CC00043228 /* DAppAction.swift in Sources */, 898A1A2F20B8044900ECB465 /* TradeTableViewCell.swift in Sources */, diff --git a/Neuron/Sections/Transaction/TransactionParamBuilder.swift b/Neuron/Sections/Transaction/TransactionParamBuilder.swift index 5c0826d2..29de0665 100644 --- a/Neuron/Sections/Transaction/TransactionParamBuilder.swift +++ b/Neuron/Sections/Transaction/TransactionParamBuilder.swift @@ -143,20 +143,13 @@ class TransactionParamBuilder: NSObject { } private func fetchTokenPrice(token: TokenModel) { - let currencyToken = CurrencyService().searchCurrencyId(for: token.symbol) - guard let tokenId = currencyToken?.id else { - return - } let currency = LocalCurrencyService.shared.getLocalCurrencySelect() - currencySymbol = currency.symbol - CurrencyService().getCurrencyPrice(tokenid: tokenId, currencyType: currency.short, completion: { (result) in - switch result { - case .success(let price): + let symbol = token.symbol + DispatchQueue.global().async { + if let price = TokenPriceLoader().getPrice(symbol: symbol, currency: currency.short) { self.tokenPrice = price - case .error: - break } - }) + } } private func rebuildGasCalculator() { diff --git a/Neuron/Sections/Wallet/Home/WalletPresenter.swift b/Neuron/Sections/Wallet/Home/WalletPresenter.swift index b3b61a34..fef3448b 100644 --- a/Neuron/Sections/Wallet/Home/WalletPresenter.swift +++ b/Neuron/Sections/Wallet/Home/WalletPresenter.swift @@ -218,19 +218,6 @@ extension WalletPresenter { // MARK: - Utils extension WalletPresenter { private func getTokenPrice(token: Token) -> Double? { - let currencyToken = CurrencyService().searchCurrencyId(for: token.symbol) - guard let tokenId = currencyToken?.id else { - return nil - } - return try? Promise.init { (resolver) in - CurrencyService().getCurrencyPrice(tokenid: tokenId, currencyType: currency.short, completion: { (result) in - switch result { - case .success(let price): - resolver.fulfill(price) - case .error(let error): - resolver.reject(error) - } - }) - }.wait() + return TokenPriceLoader().getPrice(symbol: token.symbol, currency: currency.short) } } diff --git a/Neuron/Services/Common/CoinMarketCap.swift b/Neuron/Services/Common/CoinMarketCap.swift deleted file mode 100644 index b3257779..00000000 --- a/Neuron/Services/Common/CoinMarketCap.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// CoinMarketCap.swift -// Neuron -// -// Created by 晨风 on 2018/10/24. -// Copyright © 2018 Cryptape. All rights reserved. -// - -import Foundation -import RealmSwift -import Alamofire - -class CoinMarketCapToken: Object, Decodable { - @objc dynamic var id: Int = 0 - @objc dynamic var name: String = "" - @objc dynamic var symbol: String = "" - @objc dynamic var website_slug: String = "" - - @objc override class func primaryKey() -> String? { - return "symbol" - } - - struct Response: Decodable { - let data: [CoinMarketCapToken] - } -} - -struct CoinMarketCapTicker: Decodable { - let id: Int - let name: String - let symbol: String - let website_slug: String - let quotes: [String: Quotes] - - struct Quotes: Decodable { - let price: Double - let volume_24h: Double - let market_cap: Double - let percent_change_1h: Double - let percent_change_24h: Double - let percent_change_7d: Double - } - struct Response: Decodable { - let data: CoinMarketCapTicker - } -} - -class CoinMarketCap { - enum Error: Swift.Error { - case fail - } - static let shared = CoinMarketCap() - private let realm: Realm - - private init() { - let document = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0], isDirectory: true) - let fileURL = document.appendingPathComponent("coinmarketcap") - realm = try! Realm(fileURL: fileURL) - } - - func tokenQuotes(symbol: String, currency: String = "CNY", complection: @escaping (CoinMarketCapTicker.Quotes?, Error?) -> Void) { - getTokenId(for: symbol) { (tokenId) in - guard let tokenId = tokenId else { complection(nil, Error.fail); return } - let url = URL(string: "https://api.coinmarketcap.com/v2/ticker/\(tokenId)/?convert=\(currency)")! - Alamofire.request(url).responseData { (response) in - guard let data = response.data else { complection(nil, Error.fail); return } - let response = try? JSONDecoder().decode(CoinMarketCapTicker.Response.self, from: data) - guard let ticker = response?.data else { complection(nil, Error.fail); return } - guard let quotes = ticker.quotes[currency] else { complection(nil, Error.fail); return } - complection(quotes, nil) - } - } - } - - private func getTokenId(for symbol: String, complection: @escaping (Int?) -> Void) { - if realm.objects(CoinMarketCapToken.self).count == 0 { - requestTokenList { - complection(self.realm.object(ofType: CoinMarketCapToken.self, forPrimaryKey: symbol)?.id) - } - } else { - complection(realm.object(ofType: CoinMarketCapToken.self, forPrimaryKey: symbol)?.id) - } - } - - private func requestTokenList(complection: @escaping () -> Void) { - Alamofire.request(URL(string: "https://api.coinmarketcap.com/v2/listings/")!).response(queue: DispatchQueue.global()) { (response) in - DispatchQueue.main.async { - guard let data = response.data else { complection(); return } - let response = try? JSONDecoder().decode(CoinMarketCapToken.Response.self, from: data) - guard let tokens = response?.data else { complection(); return } - try? self.realm.write { - self.realm.add(tokens, update: true) - } - complection() - } - } - } -} diff --git a/Neuron/Services/Common/CurrencyService.swift b/Neuron/Services/Common/CurrencyService.swift deleted file mode 100644 index d191238f..00000000 --- a/Neuron/Services/Common/CurrencyService.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// CurrencyService.swift -// Neuron -// -// Created by XiaoLu on 2018/9/5. -// Copyright © 2018年 Cryptape. All rights reserved. -// - -import UIKit -import Alamofire - -struct CurrencyService { - func getTokenList() -> [CurrencyToken] { - let path = Bundle.main.path(forResource: "tokens-list", ofType: "json")! - let jsonData = try? Data(contentsOf: URL(fileURLWithPath: path)) - let tokenList = try! JSONDecoder().decode(CurrencyTokenList.self, from: jsonData!) - return tokenList.data - } - - func searchCurrencyId(for symbol: String) -> CurrencyToken? { - let tokenList = getTokenList() - let newTokenList = tokenList.filter {$0.symbol == symbol} - if newTokenList.count != 0 { - return newTokenList[0] - } else { - return nil - } - } - - func getCurrencyPrice(tokenid id: Int, currencyType: String, completion: @escaping (EthServiceResult) -> Void) { - var convert = "/?convert=" + currencyType - if currencyType.count == 0 { - convert = "" - } - let path = ServerApi.currencyPriceURL + "\(id)" + convert - Alamofire.request(path, method: .get).responseJSON { (response) in - if response.error == nil { - let currencyData = try! JSONDecoder().decode(CurrencyData.self, from: response.data!) - let currencyPrice = currencyData.data.quotes[currencyType]! - completion(EthServiceResult.success(currencyPrice.price)) - } else { - completion(EthServiceResult.error(response.error!)) - } - } - } -} - -struct CurrencyTokenList: Codable { - var data: [CurrencyToken] -} - -struct CurrencyToken: Codable { - var id: Int - var name: String - var symbol: String - var website_slug: String -} - -struct CurrencyData: Codable { - var data: CurrencyQuotes -} - -struct CurrencyQuotes: Codable { - var quotes: [String: CurrencyType] -} - -struct CurrencyType: Codable { - var price: Double -} diff --git a/Neuron/Services/Common/TokenPriceLoader.swift b/Neuron/Services/Common/TokenPriceLoader.swift new file mode 100644 index 00000000..6bed3c69 --- /dev/null +++ b/Neuron/Services/Common/TokenPriceLoader.swift @@ -0,0 +1,80 @@ +// +// TokenPriceLoader.swift +// Neuron +// +// Created by 晨风 on 2018/11/29. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import RealmSwift +import Alamofire +import PromiseKit + +class TokenPriceLoader { + private class Token: Decodable { + var id: Int = 0 + var name: String = "" + var symbol: String = "" + } + + private struct TokenListResponse: Decodable { + let data: [Token] + } + + private struct TokenQuotes: Decodable { + let id: Int + let name: String + let symbol: String + let quotes: [String: Quotes] + } + + private struct Quotes: Decodable { + let price: Double + } + + private struct TokenQuotesResponse: Decodable { + let data: TokenQuotes + } + + private static var tokens = [Token]() + + func getPrice(symbol: String, currency: String) -> Double? { + guard let tokenId = getTokenId(symbol: symbol) else { return nil } + let url = URL(string: "https://api.coinmarketcap.com/v2/ticker/\(tokenId)/?convert=\(currency)")! + return try? Promise.init { (resolver) in + Alamofire.request(url).responseData { (response) in + do { + guard let data = response.data else { throw response.error! } + let response = try JSONDecoder().decode(TokenQuotesResponse.self, from: data) + let ticker = response.data + let quotes = ticker.quotes[currency] + resolver.fulfill(quotes!.price) + } catch { + resolver.reject(error) + } + } + }.wait() + } + + private func getTokenId(symbol: String) -> Int? { + return (try? getTokenList())?.first(where: { $0.symbol == symbol })?.id + } + + private func getTokenList() throws -> [Token] { + if TokenPriceLoader.tokens.count == 0 { + TokenPriceLoader.tokens = (try? Promise<[Token]>.init { (resolver) in + Alamofire.request(URL(string: "https://api.coinmarketcap.com/v2/listings/")!).response(queue: DispatchQueue.global()) { (response) in + do { + guard let data = response.data else { throw response.error! } + let response = try JSONDecoder().decode(TokenListResponse.self, from: data) + resolver.fulfill(response.data) + } catch { + resolver.reject(error) + } + } + }.wait()) ?? [] + } + return TokenPriceLoader.tokens + } +} diff --git a/Neuron/Services/Ethereum/TokenProfile.swift b/Neuron/Services/Ethereum/TokenProfile.swift index 712caa45..6994c6b3 100644 --- a/Neuron/Services/Ethereum/TokenProfile.swift +++ b/Neuron/Services/Ethereum/TokenProfile.swift @@ -48,27 +48,21 @@ extension TokenModel { private func getEthereumProfile(complection: @escaping (TokenProfile?) -> Void) { let overview = TokenProfile.Overview(zh: "Ethereum是一个运行智能合约的去中心化平台,应用将完全按照程序运作,不存在任何欺诈,审查与第三方干预的可能。") let detailUrl = URL(string: "https://ntp.staging.cryptape.com?coin=ethereum") - var profile = TokenProfile(symbol: symbol, address: address, overview: overview, imageUrl: nil, image: UIImage(named: "eth_logo"), possess: nil, detailUrl: detailUrl, price: nil) + var profile = TokenProfile(symbol: self.symbol, address: address, overview: overview, imageUrl: nil, image: UIImage(named: "eth_logo"), possess: nil, detailUrl: detailUrl, price: nil) - let currency = CurrencyService() - let currencyToken = currency.searchCurrencyId(for: symbol) - guard let tokenId = currencyToken?.id else { - complection(profile) - return - } let currencyType = LocalCurrencyService.shared.getLocalCurrencySelect().short - currency.getCurrencyPrice(tokenid: tokenId, currencyType: currencyType) { (result) in - switch result { - case .success(let value): - let balance = self.tokenBalance - let amount = balance * value - let possess = String(format: "%@ %.2f", LocalCurrencyService.shared.getLocalCurrencySelect().symbol, amount) - profile.possess = possess - profile.price = value - default: - break + let symbol = self.symbol + DispatchQueue.global().async { + let price = TokenPriceLoader().getPrice(symbol: symbol, currency: currencyType) + DispatchQueue.main.async { + if let price = price { + let amount = self.tokenBalance * price + let possess = String(format: "%@ %.2f", LocalCurrencyService.shared.getLocalCurrencySelect().symbol, amount) + profile.possess = possess + profile.price = price + } + complection(profile) } - complection(profile) } } @@ -91,9 +85,10 @@ extension TokenModel { group.enter() let currency = LocalCurrencyService.shared.getLocalCurrencySelect() - CoinMarketCap.shared.tokenQuotes(symbol: symbol, currency: currency.short) { (quotes, _) in - defer { group.leave() } - price = quotes?.price + let symbol = self.symbol + DispatchQueue.global().async { + price = TokenPriceLoader().getPrice(symbol: symbol, currency: currency.short) + group.leave() } group.notify(queue: .main) { From 8088e66ed4b881f14abba2eca1966b0d42063f69 Mon Sep 17 00:00:00 2001 From: cezres Date: Fri, 30 Nov 2018 14:08:29 +0800 Subject: [PATCH 281/315] Remove `EthServiceResult` --- .../Dapp/Native/NFTViewController.swift | 27 +++--- .../Wallet/Assets/AddAssetController.swift | 22 ++--- Neuron/Services/Common/ServiceErrors.swift | 6 -- .../Ethereum/CustomERC20TokenService.swift | 89 +++++++------------ .../Services/Ethereum/ERC20TokenService.swift | 77 ---------------- Neuron/Services/Ethereum/NFTService.swift | 19 ++-- 6 files changed, 71 insertions(+), 169 deletions(-) delete mode 100644 Neuron/Services/Ethereum/ERC20TokenService.swift diff --git a/Neuron/Sections/Dapp/Native/NFTViewController.swift b/Neuron/Sections/Dapp/Native/NFTViewController.swift index 0c8219da..e106b29f 100644 --- a/Neuron/Sections/Dapp/Native/NFTViewController.swift +++ b/Neuron/Sections/Dapp/Native/NFTViewController.swift @@ -26,22 +26,23 @@ class NFTViewController: UITableViewController, ErrorOverlayPresentable { dataArray.removeAll() let appModel = AppModel.current let address = appModel.currentWallet!.address - let nftService = NFTService() - nftService.getErc721Data(with: address) { (result) in - switch result { - case .success(let nftModel): - self.dataArray = nftModel.assets ?? [] - if self.dataArray.count == 0 { - self.showBlankOverlay() + DispatchQueue.global().async { + let result = try? NFTService().getErc721Data(with: address) + DispatchQueue.main.async { + if let nftModel = result { + self.dataArray = nftModel.assets ?? [] + if self.dataArray.count == 0 { + self.showBlankOverlay() + } else { + self.removeOverlay() + } } else { - self.removeOverlay() + Toast.showToast(text: "网络错误,请稍后再试.") + self.showNetworkFailOverlay() } - case .error: - Toast.showToast(text: "网络错误,请稍后再试.") - self.showNetworkFailOverlay() + self.tableView.reloadData() + self.tableView.refreshControl?.endRefreshing() } - self.tableView.reloadData() - self.tableView.refreshControl?.endRefreshing() } } diff --git a/Neuron/Sections/Wallet/Assets/AddAssetController.swift b/Neuron/Sections/Wallet/Assets/AddAssetController.swift index 13f0a077..977c9aa2 100644 --- a/Neuron/Sections/Wallet/Assets/AddAssetController.swift +++ b/Neuron/Sections/Wallet/Assets/AddAssetController.swift @@ -143,18 +143,20 @@ class AddAssetController: UIViewController, UITableViewDelegate, UITableViewData } func didGetERC20Token(token: String) { - let appmodel = AppModel.current + let walletAddress = AppModel.current.currentWallet!.address Toast.showHUD() - ERC20TokenService.addERC20TokenToApp(contractAddress: token, walletAddress: (appmodel.currentWallet?.address)!) { (result) in - Toast.hideHUD() - switch result { - case .success(let tokenM): - self.tokenModel = tokenM - self.tokenModel.address = token - case .error: - Toast.showToast(text: "未查询到代币信息,请核对合约地址是否正确") + DispatchQueue.global().async { + let result = try? CustomERC20TokenService.searchTokenData(contractAddress: token, walletAddress: walletAddress) + DispatchQueue.main.async { + Toast.hideHUD() + if let tokenModel = result { + self.tokenModel = tokenModel + self.tokenModel.address = token + } else { + Toast.showToast(text: "未查询到代币信息,请核对合约地址是否正确") + } + self.aTable.reloadData() } - self.aTable.reloadData() } } } diff --git a/Neuron/Services/Common/ServiceErrors.swift b/Neuron/Services/Common/ServiceErrors.swift index e25c38c2..40f0e879 100644 --- a/Neuron/Services/Common/ServiceErrors.swift +++ b/Neuron/Services/Common/ServiceErrors.swift @@ -8,12 +8,6 @@ import Foundation -// TODO: Remove result type -enum EthServiceResult { - case success(T) - case error(Error) -} - enum CustomTokenError: String, LocalizedError { case wrongBalanceError case badNameError diff --git a/Neuron/Services/Ethereum/CustomERC20TokenService.swift b/Neuron/Services/Ethereum/CustomERC20TokenService.swift index b231f387..f1383aa7 100644 --- a/Neuron/Services/Ethereum/CustomERC20TokenService.swift +++ b/Neuron/Services/Ethereum/CustomERC20TokenService.swift @@ -12,69 +12,49 @@ import EthereumAddress import struct BigInt.BigUInt struct CustomERC20TokenService { - static func decimals(walletAddress: String, token: String, completion: @escaping (EthServiceResult) -> Void) { - DispatchQueue.global(qos: .userInitiated).async { - let contract = self.contract(ERC20Token: token)! - var options = TransactionOptions() - options.from = EthereumAddress(walletAddress) - let transaction = contract.method("decimals", parameters: [AnyObject]())! - do { - let decimals = try transaction.call(transactionOptions: options) - if let decimals = decimals["0"] as? BigUInt { - DispatchQueue.main.async { - completion(EthServiceResult.success(decimals)) - } - } else { - throw CustomTokenError.wrongBalanceError - } - } catch { - DispatchQueue.main.async { - completion(EthServiceResult.error(CustomTokenError.wrongBalanceError)) - } - } + static func searchTokenData(contractAddress: String, walletAddress: String) throws -> TokenModel { + let contractAddress = contractAddress.addHexPrefix() + guard EthereumAddress(contractAddress) != nil else { + throw CustomTokenError.undefinedError } - } - static func name(walletAddress: String, token: String, completion: @escaping (EthServiceResult) -> Void) { - let contract = self.contract(ERC20Token: token)! - if let transaction = contract.method("name", parameters: [AnyObject]()) { - var options = TransactionOptions() - options.from = EthereumAddress(walletAddress) - do { - let name = try transaction.call(transactionOptions: options) - if let names = name["0"] as? String, !names.isEmpty { - completion(EthServiceResult.success(names)) - } else { - throw CustomTokenError.badNameError - } - } catch { - completion(EthServiceResult.error(CustomTokenError.badNameError)) - } + let tokenModel = TokenModel() + if let name = callTransaction(contractAddress: contractAddress, walletAddress: walletAddress, method: "name") as? String { + tokenModel.name = name + } else { + throw CustomTokenError.badNameError + } + + if let symbol = callTransaction(contractAddress: contractAddress, walletAddress: walletAddress, method: "symbol") as? String { + tokenModel.symbol = symbol } else { - completion(EthServiceResult.error(CustomTokenError.badNameError)) + throw CustomTokenError.badSymbolError } + + if let decimals = callTransaction(contractAddress: contractAddress, walletAddress: walletAddress, method: "decimals") as? BigUInt { + tokenModel.decimals = Int(decimals) + } else { + throw CustomTokenError.wrongBalanceError + } + + guard !tokenModel.name.isEmpty, !tokenModel.symbol.isEmpty else { + throw CustomTokenError.undefinedError + } + return tokenModel } - static func symbol(walletAddress: String, token: String, completion: @escaping (EthServiceResult) -> Void) { - DispatchQueue.global(qos: .userInitiated).async { - let contract = self.contract(ERC20Token: token)! - let transaction = contract.method("symbol", parameters: [AnyObject]())! + private static func callTransaction(contractAddress: String, walletAddress: String, method: String) -> Any? { + let contract = self.contract(ERC20Token: contractAddress)! + if let transaction = contract.method(method, parameters: [AnyObject]()) { var options = TransactionOptions() options.from = EthereumAddress(walletAddress) - do { - let symbol = try transaction.call(transactionOptions: options) - if let symbol = symbol["0"] as? String, !symbol.isEmpty { - DispatchQueue.main.async { - completion(EthServiceResult.success(symbol)) - } - } else { - throw CustomTokenError.badSymbolError - } - } catch { - DispatchQueue.main.async { - completion(EthServiceResult.error(CustomTokenError.badSymbolError)) - } + if let result = try? transaction.call(transactionOptions: options) { + return result["0"] + } else { + return nil } + } else { + return nil } } @@ -91,5 +71,4 @@ struct CustomERC20TokenService { options.from = EthereumAddress(wAddress) return options } - } diff --git a/Neuron/Services/Ethereum/ERC20TokenService.swift b/Neuron/Services/Ethereum/ERC20TokenService.swift deleted file mode 100644 index eef9ce39..00000000 --- a/Neuron/Services/Ethereum/ERC20TokenService.swift +++ /dev/null @@ -1,77 +0,0 @@ -// -// ERC20TokenService.swift -// Neuron -// -// Created by XiaoLu on 2018/7/4. -// Copyright © 2018年 cryptape. All rights reserved. -// - -import Foundation -import Web3swift -import EthereumAddress -import struct BigInt.BigUInt - -struct ERC20TokenService { - /// search ETC20Token data for contractaddress - /// - /// - Parameters: - /// - contractAddress: contractAddress - /// - walletAddress: walletAddress - /// - completion: result - static func addERC20TokenToApp(contractAddress: String, walletAddress: String, completion:@escaping (EthServiceResult) -> Void) { - var ethAddress: EthereumAddress? - let cAddress = contractAddress.addHexPrefix() - ethAddress = EthereumAddress(cAddress) - - guard ethAddress != nil else { - completion(EthServiceResult.error(CustomTokenError.undefinedError)) - return - } - - let tokenModel = TokenModel() - DispatchQueue.global(qos: .userInitiated).async { - let disGroup = DispatchGroup() - - disGroup.enter() - CustomERC20TokenService.name(walletAddress: walletAddress, token: cAddress, completion: { (result) in - switch result { - case .success(let name): - tokenModel.name = name - case .error: - break - } - disGroup.leave() - }) - - disGroup.enter() - CustomERC20TokenService.symbol(walletAddress: walletAddress, token: cAddress, completion: { (result) in - switch result { - case .success(let symbol): - tokenModel.symbol = symbol - case .error: - break - } - disGroup.leave() - }) - - disGroup.enter() - CustomERC20TokenService.decimals(walletAddress: walletAddress, token: cAddress, completion: { (result) in - switch result { - case .success(let decimals): - tokenModel.decimals = Int(decimals) - case .error: - break - } - disGroup.leave() - }) - - disGroup.notify(queue: .main) { - guard !tokenModel.name.isEmpty, !tokenModel.symbol.isEmpty else { - completion(EthServiceResult.error(CustomTokenError.undefinedError)) - return - } - completion(EthServiceResult.success(tokenModel)) - } - } - } -} diff --git a/Neuron/Services/Ethereum/NFTService.swift b/Neuron/Services/Ethereum/NFTService.swift index d80055e3..27a16827 100644 --- a/Neuron/Services/Ethereum/NFTService.swift +++ b/Neuron/Services/Ethereum/NFTService.swift @@ -8,18 +8,21 @@ import UIKit import Alamofire +import PromiseKit struct NFTService { - func getErc721Data(with address: String, completion: @escaping (EthServiceResult) -> Void) { + func getErc721Data(with address: String) throws -> NFTModel { let url = ServerApi.openseaURL + address - Alamofire.request(url, method: .get).responseJSON { (response) in - if response.error == nil { - let nftModel = try! JSONDecoder().decode(NFTModel.self, from: response.data!) - completion(EthServiceResult.success(nftModel)) - } else { - completion(EthServiceResult.error(response.error!)) + return try Promise.init { (resolver) in + Alamofire.request(url, method: .get).responseJSON { (response) in + if response.error == nil { + let nftModel = try! JSONDecoder().decode(NFTModel.self, from: response.data!) + resolver.fulfill(nftModel) + } else { + resolver.reject(response.error!) + } } - } + }.wait() } } From a6bf3e647ccf1d488810ca13550770bdfb11c56e Mon Sep 17 00:00:00 2001 From: cezres Date: Fri, 30 Nov 2018 14:14:43 +0800 Subject: [PATCH 282/315] Update project.pbxproj --- Neuron.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index e32b2f7a..7c718c82 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -192,7 +192,6 @@ 89F9E73E20DEBDE8009E68D4 /* ExportKeystoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89F9E73C20DEBDE8009E68D4 /* ExportKeystoreController.swift */; }; 89F9E73F20DEBDE8009E68D4 /* ExportKeystoreController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 89F9E73D20DEBDE8009E68D4 /* ExportKeystoreController.xib */; }; 89FC542D20D4142D00D5D27C /* SkipBackupFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FC542C20D4142D00D5D27C /* SkipBackupFiles.swift */; }; - 89FE21D820EC7E1800A09302 /* ERC20TokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FE21D720EC7E1800A09302 /* ERC20TokenService.swift */; }; 89FE21DA20EDB10900A09302 /* CustomERC20TokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FE21D920EDB10900A09302 /* CustomERC20TokenService.swift */; }; 89FE21DE20EDB70000A09302 /* ServiceErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FE21DD20EDB70000A09302 /* ServiceErrors.swift */; }; E2E67496168FEDCBC828A891 /* Pods_NeuronUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3502E1744CFA926363F980D0 /* Pods_NeuronUITests.framework */; }; @@ -414,7 +413,6 @@ 89F9E73C20DEBDE8009E68D4 /* ExportKeystoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportKeystoreController.swift; sourceTree = ""; }; 89F9E73D20DEBDE8009E68D4 /* ExportKeystoreController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExportKeystoreController.xib; sourceTree = ""; }; 89FC542C20D4142D00D5D27C /* SkipBackupFiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SkipBackupFiles.swift; sourceTree = ""; }; - 89FE21D720EC7E1800A09302 /* ERC20TokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ERC20TokenService.swift; sourceTree = ""; }; 89FE21D920EDB10900A09302 /* CustomERC20TokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomERC20TokenService.swift; sourceTree = ""; }; 89FE21DD20EDB70000A09302 /* ServiceErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceErrors.swift; sourceTree = ""; }; 8CC6FD685582D038A3767657 /* Pods_Neuron.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Neuron.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -522,7 +520,6 @@ 89BEBE6A20EF82B6007D2705 /* EthereumNetwork.swift */, 1A69465B21916BE5000093A2 /* EthereumTxSender.swift */, 6E8BFC3021AFDBB5004752CC /* EthereumBalanceLoader.swift */, - 89FE21D720EC7E1800A09302 /* ERC20TokenService.swift */, 89FE21D920EDB10900A09302 /* CustomERC20TokenService.swift */, 89733043217ED041001B8D93 /* ETHSignMessageService.swift */, 89D84CE021475755006B0287 /* NFTService.swift */, @@ -1411,7 +1408,6 @@ 898A1A0F20B6539800ECB465 /* AddAssetTableViewCell.swift in Sources */, 6E9B947B21A4080300D67357 /* DesignableButton.swift in Sources */, 89A87C892173097000588674 /* WalletNameValidator.swift in Sources */, - 89FE21D820EC7E1800A09302 /* ERC20TokenService.swift in Sources */, 6E3B2B6B219D938F0095257D /* SentTransaction.swift in Sources */, 1A5515E521898A1000D34791 /* WalletKeystoreManager.swift in Sources */, 6EABFB10219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift in Sources */, From c3fe092e420788e57333228c27ee0c59819e3269 Mon Sep 17 00:00:00 2001 From: James Chen Date: Fri, 30 Nov 2018 15:26:48 +0900 Subject: [PATCH 283/315] Remove PromiseKit import from WalletPresenter --- Neuron/Sections/Wallet/Home/WalletPresenter.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Neuron/Sections/Wallet/Home/WalletPresenter.swift b/Neuron/Sections/Wallet/Home/WalletPresenter.swift index fef3448b..0489606d 100644 --- a/Neuron/Sections/Wallet/Home/WalletPresenter.swift +++ b/Neuron/Sections/Wallet/Home/WalletPresenter.swift @@ -9,7 +9,6 @@ import UIKit import RealmSwift import BigInt -import PromiseKit import Web3swift import EthereumAddress From a2cbb532465f17a289a443b807344f9796d7fcd8 Mon Sep 17 00:00:00 2001 From: James Chen Date: Fri, 30 Nov 2018 15:44:54 +0900 Subject: [PATCH 284/315] Remove SignMessageResult --- .../Dapp/WebView/MessageSignController.swift | 38 +++++++++------ Neuron/Services/Common/ServiceErrors.swift | 6 --- .../Ethereum/ETHSignMessageService.swift | 48 ++++++------------- 3 files changed, 38 insertions(+), 54 deletions(-) diff --git a/Neuron/Sections/Dapp/WebView/MessageSignController.swift b/Neuron/Sections/Dapp/WebView/MessageSignController.swift index 3a849760..e98c6867 100644 --- a/Neuron/Sections/Dapp/WebView/MessageSignController.swift +++ b/Neuron/Sections/Dapp/WebView/MessageSignController.swift @@ -126,25 +126,35 @@ private extension MessageSignController { } func ethSignPersonalMessage(password: String) { - ETHSignMessageService.signPersonal(message: dappCommonModel.eth?.data ?? "", password: password) { (result) in - Toast.hideHUD() - switch result { - case .success(let signed): - self.finish(signed: signed) - case .error(let error): - self.showSignError(error.localizedDescription) + DispatchQueue.global().async { + do { + let signed = try ETHSignMessageService.signPersonal(message: self.dappCommonModel.eth?.data ?? "", password: password) + DispatchQueue.main.async { + Toast.hideHUD() + self.finish(signed: signed) + } + } catch let error { + DispatchQueue.main.async { + Toast.hideHUD() + self.showSignError(error.localizedDescription) + } } } } func ethSignMessage(password: String) { - ETHSignMessageService.sign(message: dappCommonModel.eth?.data ?? "", password: password) { (result) in - Toast.hideHUD() - switch result { - case .success(let signed): - self.finish(signed: signed) - case .error(let error): - self.showSignError(error.localizedDescription) + DispatchQueue.global().async { + do { + let signed = try ETHSignMessageService.sign(message: self.dappCommonModel.eth?.data ?? "", password: password) + DispatchQueue.main.async { + Toast.hideHUD() + self.finish(signed: signed) + } + } catch let error { + DispatchQueue.main.async { + Toast.hideHUD() + self.showSignError(error.localizedDescription) + } } } } diff --git a/Neuron/Services/Common/ServiceErrors.swift b/Neuron/Services/Common/ServiceErrors.swift index 40f0e879..3083069d 100644 --- a/Neuron/Services/Common/ServiceErrors.swift +++ b/Neuron/Services/Common/ServiceErrors.swift @@ -19,12 +19,6 @@ enum CustomTokenError: String, LocalizedError { } } -// TODO: Remove result type -enum SignMessageResult { - case success(T) - case error(Error) -} - enum SendTransactionError: String, LocalizedError { case invalidSourceAddress case invalidDestinationAddress diff --git a/Neuron/Services/Ethereum/ETHSignMessageService.swift b/Neuron/Services/Ethereum/ETHSignMessageService.swift index 1f2f86b8..5c82990c 100644 --- a/Neuron/Services/Ethereum/ETHSignMessageService.swift +++ b/Neuron/Services/Ethereum/ETHSignMessageService.swift @@ -16,51 +16,31 @@ struct ETHSignMessageService { case signMessageFailed } - public static func sign(message: String, password: String, completion: @escaping (SignMessageResult) -> Void) { + public static func sign(message: String, password: String) throws -> String { let messageData = Data.fromHex(message) ?? Data() let walletModel = AppModel.current.currentWallet! guard let wallet = walletModel.wallet else { - completion(SignMessageResult.error(Error.walletIsNull)) - return + throw Error.walletIsNull } - DispatchQueue.global().async { - do { - let privateKey = try WalletManager.default.exportPrivateKey(wallet: wallet, password: password) - guard let signed = try EthereumMessageSigner().sign(message: messageData, privateKey: privateKey) else { - throw Error.signMessageFailed - } - DispatchQueue.main.async { - completion(SignMessageResult.success(signed)) - } - } catch let error { - DispatchQueue.main.async { - completion(SignMessageResult.error(error)) - } - } + + let privateKey = try WalletManager.default.exportPrivateKey(wallet: wallet, password: password) + guard let signed = try EthereumMessageSigner().sign(message: messageData, privateKey: privateKey) else { + throw Error.signMessageFailed } + return signed } - public static func signPersonal(message: String, password: String, completion: @escaping (SignMessageResult) -> Void) { + public static func signPersonal(message: String, password: String) throws -> String { let messageData = Data.fromHex(message) ?? Data() let walletModel = AppModel.current.currentWallet! guard let wallet = walletModel.wallet else { - completion(SignMessageResult.error(Error.walletIsNull)) - return + throw Error.walletIsNull } - DispatchQueue.global().async { - do { - let privateKey = try WalletManager.default.exportPrivateKey(wallet: wallet, password: password) - guard let signed = try EthereumMessageSigner().signPersonalMessage(message: messageData, privateKey: privateKey) else { - throw Error.signMessageFailed - } - DispatchQueue.main.async { - completion(SignMessageResult.success(signed)) - } - } catch let error { - DispatchQueue.main.async { - completion(SignMessageResult.error(error)) - } - } + + let privateKey = try WalletManager.default.exportPrivateKey(wallet: wallet, password: password) + guard let signed = try EthereumMessageSigner().signPersonalMessage(message: messageData, privateKey: privateKey) else { + throw Error.signMessageFailed } + return signed } } From 494d8c38d9cc2266a5664fc18baa391415161098 Mon Sep 17 00:00:00 2001 From: James Chen Date: Fri, 30 Nov 2018 16:02:37 +0900 Subject: [PATCH 285/315] Localize ETHSignMessageService.Error --- .../Services/Ethereum/ETHSignMessageService.swift | 13 ++++++++----- Neuron/en.lproj/Localizable.strings | 5 +++++ Neuron/zh-Hans.lproj/Localizable.strings | 5 +++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Neuron/Services/Ethereum/ETHSignMessageService.swift b/Neuron/Services/Ethereum/ETHSignMessageService.swift index 5c82990c..388b84d3 100644 --- a/Neuron/Services/Ethereum/ETHSignMessageService.swift +++ b/Neuron/Services/Ethereum/ETHSignMessageService.swift @@ -10,17 +10,20 @@ import Foundation import AppChain struct ETHSignMessageService { - enum Error: Swift.Error { - case walletIsNull - case privateKeyIsNull + enum Error: String, LocalizedError { + case walletNotFound case signMessageFailed + + var errorDescription: String? { + return "ETHSignMessageService.\(rawValue)".localized() + } } public static func sign(message: String, password: String) throws -> String { let messageData = Data.fromHex(message) ?? Data() let walletModel = AppModel.current.currentWallet! guard let wallet = walletModel.wallet else { - throw Error.walletIsNull + throw Error.walletNotFound } let privateKey = try WalletManager.default.exportPrivateKey(wallet: wallet, password: password) @@ -34,7 +37,7 @@ struct ETHSignMessageService { let messageData = Data.fromHex(message) ?? Data() let walletModel = AppModel.current.currentWallet! guard let wallet = walletModel.wallet else { - throw Error.walletIsNull + throw Error.walletNotFound } let privateKey = try WalletManager.default.exportPrivateKey(wallet: wallet, password: password) diff --git a/Neuron/en.lproj/Localizable.strings b/Neuron/en.lproj/Localizable.strings index 9f659e1a..27dc1e61 100644 --- a/Neuron/en.lproj/Localizable.strings +++ b/Neuron/en.lproj/Localizable.strings @@ -64,6 +64,11 @@ "CustomTokenError.badSymbolError" = "Bad token symbol"; "CustomTokenError.undefinedError" = "Undefined error"; +// Ethereum Sign Message Error +// --------------------------------------------------------- +"ETHSignMessageService.walletNotFound" = "Wallet not found"; +"ETHSignMessageService.signMessageFailed" = "Failed to sign the message"; + // Wallet // --------------------------------------------------------- "Wallet" = "钱包"; diff --git a/Neuron/zh-Hans.lproj/Localizable.strings b/Neuron/zh-Hans.lproj/Localizable.strings index 02bf1941..1e3c28bd 100644 --- a/Neuron/zh-Hans.lproj/Localizable.strings +++ b/Neuron/zh-Hans.lproj/Localizable.strings @@ -63,6 +63,11 @@ "CustomTokenError.badSymbolError" = "Token symbol不正确"; "CustomTokenError.undefinedError" = "未知错误"; +// Ethereum Sign Message Error +// --------------------------------------------------------- +"ETHSignMessageService.walletNotFound" = "未找到该钱包"; +"ETHSignMessageService.signMessageFailed" = "签名失败"; + // Wallet // --------------------------------------------------------- "Wallet" = "钱包"; From 60179513cd502d0d7c60727d876e18f7946f1d99 Mon Sep 17 00:00:00 2001 From: James Chen Date: Fri, 30 Nov 2018 16:08:57 +0900 Subject: [PATCH 286/315] Delete ServiceErrors moving error types to related modules --- Neuron.xcodeproj/project.pbxproj | 8 ++++---- ...rviceErrors.swift => SendTransactionError.swift} | 13 +------------ .../Services/Ethereum/CustomERC20TokenService.swift | 13 ++++++++++++- 3 files changed, 17 insertions(+), 17 deletions(-) rename Neuron/Services/Common/{ServiceErrors.swift => SendTransactionError.swift} (65%) diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 7c718c82..2ff970ae 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -193,7 +193,7 @@ 89F9E73F20DEBDE8009E68D4 /* ExportKeystoreController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 89F9E73D20DEBDE8009E68D4 /* ExportKeystoreController.xib */; }; 89FC542D20D4142D00D5D27C /* SkipBackupFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FC542C20D4142D00D5D27C /* SkipBackupFiles.swift */; }; 89FE21DA20EDB10900A09302 /* CustomERC20TokenService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FE21D920EDB10900A09302 /* CustomERC20TokenService.swift */; }; - 89FE21DE20EDB70000A09302 /* ServiceErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FE21DD20EDB70000A09302 /* ServiceErrors.swift */; }; + 89FE21DE20EDB70000A09302 /* SendTransactionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89FE21DD20EDB70000A09302 /* SendTransactionError.swift */; }; E2E67496168FEDCBC828A891 /* Pods_NeuronUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3502E1744CFA926363F980D0 /* Pods_NeuronUITests.framework */; }; FEA60F4E8F81CB701C31AE44 /* Pods_NeuronTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB1ED6C1CFAD097B034BA2EF /* Pods_NeuronTests.framework */; }; /* End PBXBuildFile section */ @@ -414,7 +414,7 @@ 89F9E73D20DEBDE8009E68D4 /* ExportKeystoreController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExportKeystoreController.xib; sourceTree = ""; }; 89FC542C20D4142D00D5D27C /* SkipBackupFiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SkipBackupFiles.swift; sourceTree = ""; }; 89FE21D920EDB10900A09302 /* CustomERC20TokenService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomERC20TokenService.swift; sourceTree = ""; }; - 89FE21DD20EDB70000A09302 /* ServiceErrors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServiceErrors.swift; sourceTree = ""; }; + 89FE21DD20EDB70000A09302 /* SendTransactionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendTransactionError.swift; sourceTree = ""; }; 8CC6FD685582D038A3767657 /* Pods_Neuron.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Neuron.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C43FB42A510FDF667FD64169 /* Pods-NeuronUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NeuronUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-NeuronUITests/Pods-NeuronUITests.debug.xcconfig"; sourceTree = ""; }; DB16ADF7FEC17385D266EA00 /* Pods-NeuronUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NeuronUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-NeuronUITests/Pods-NeuronUITests.release.xcconfig"; sourceTree = ""; }; @@ -1037,12 +1037,12 @@ 898CA98420EA21E90059ECA3 /* Common */ = { isa = PBXGroup; children = ( - 89FE21DD20EDB70000A09302 /* ServiceErrors.swift */, 89C6145D2141038300DC3DF4 /* LocalCurrencyService.swift */, 6E90D06821704C1D00B98363 /* SensorsAnalytics.swift */, 6E3B2B6A219D938F0095257D /* SentTransaction.swift */, 6E3B2B74219EDE200095257D /* TransactionStatusManager.swift */, 6E8BFC3621B00948004752CC /* TokenPriceLoader.swift */, + 89FE21DD20EDB70000A09302 /* SendTransactionError.swift */, ); path = Common; sourceTree = ""; @@ -1513,7 +1513,7 @@ 1A6946602192A0B0000093A2 /* GasCalculator.swift in Sources */, 89D84CED2148C056006B0287 /* NFTFooterReusableView.swift in Sources */, 1A092245213580C900CAED5D /* NFTViewController.swift in Sources */, - 89FE21DE20EDB70000A09302 /* ServiceErrors.swift in Sources */, + 89FE21DE20EDB70000A09302 /* SendTransactionError.swift in Sources */, 891C2483211165AC007C639D /* TokenMacro.swift in Sources */, 6E3B2B75219EDE200095257D /* TransactionStatusManager.swift in Sources */, 6E9D7CBA216B04EF0044176D /* AuthenticationService.swift in Sources */, diff --git a/Neuron/Services/Common/ServiceErrors.swift b/Neuron/Services/Common/SendTransactionError.swift similarity index 65% rename from Neuron/Services/Common/ServiceErrors.swift rename to Neuron/Services/Common/SendTransactionError.swift index 3083069d..4251e498 100644 --- a/Neuron/Services/Common/ServiceErrors.swift +++ b/Neuron/Services/Common/SendTransactionError.swift @@ -1,5 +1,5 @@ // -// ServiceErrors.swift +// SendTransactionError.swift // Neuron // // Created by XiaoLu on 2018/7/5. @@ -8,17 +8,6 @@ import Foundation -enum CustomTokenError: String, LocalizedError { - case wrongBalanceError - case badNameError - case badSymbolError - case undefinedError - - var errorDescription: String? { - return "CustomTokenError.\(rawValue)".localized() - } -} - enum SendTransactionError: String, LocalizedError { case invalidSourceAddress case invalidDestinationAddress diff --git a/Neuron/Services/Ethereum/CustomERC20TokenService.swift b/Neuron/Services/Ethereum/CustomERC20TokenService.swift index f1383aa7..2cc6d89b 100644 --- a/Neuron/Services/Ethereum/CustomERC20TokenService.swift +++ b/Neuron/Services/Ethereum/CustomERC20TokenService.swift @@ -9,7 +9,18 @@ import Foundation import Web3swift import EthereumAddress -import struct BigInt.BigUInt +import BigInt + +enum CustomTokenError: String, LocalizedError { + case wrongBalanceError + case badNameError + case badSymbolError + case undefinedError + + var errorDescription: String? { + return "CustomTokenError.\(rawValue)".localized() + } +} struct CustomERC20TokenService { static func searchTokenData(contractAddress: String, walletAddress: String) throws -> TokenModel { From 983beb324a4f3c8539374df989ecbfdd28191852 Mon Sep 17 00:00:00 2001 From: James Chen Date: Fri, 30 Nov 2018 17:39:51 +0900 Subject: [PATCH 287/315] Implement EthereumMessageSigner as the previous used one has been removed from AppChain SDK --- Neuron.xcodeproj/project.pbxproj | 4 ++ .../Ethereum/EthereumMessageSigner.swift | 59 +++++++++++++++++++ Podfile | 2 +- Podfile.lock | 12 ++-- 4 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 Neuron/Services/Ethereum/EthereumMessageSigner.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 2ff970ae..74e4c5e6 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 1A092245213580C900CAED5D /* NFTViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A092244213580C900CAED5D /* NFTViewController.swift */; }; + 1A19C2DE21B12DE800C289D0 /* EthereumMessageSigner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A19C2DD21B12DE800C289D0 /* EthereumMessageSigner.swift */; }; 1A1E1B8B21A3CF3A00C24D10 /* SignMessagePageItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A1E1B8A21A3CF3A00C24D10 /* SignMessagePageItem.swift */; }; 1A1E841821717EBD00B15E88 /* PasswordValidatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A1E841721717EBD00B15E88 /* PasswordValidatorTests.swift */; }; 1A207DD32137B4B7008DC306 /* SwitchWallet.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1A207DD22137B4B7008DC306 /* SwitchWallet.storyboard */; }; @@ -217,6 +218,7 @@ /* Begin PBXFileReference section */ 1A092244213580C900CAED5D /* NFTViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NFTViewController.swift; sourceTree = ""; }; + 1A19C2DD21B12DE800C289D0 /* EthereumMessageSigner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumMessageSigner.swift; sourceTree = ""; }; 1A1E1B8A21A3CF3A00C24D10 /* SignMessagePageItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignMessagePageItem.swift; sourceTree = ""; }; 1A1E841721717EBD00B15E88 /* PasswordValidatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordValidatorTests.swift; sourceTree = ""; }; 1A207DD22137B4B7008DC306 /* SwitchWallet.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SwitchWallet.storyboard; sourceTree = ""; }; @@ -521,6 +523,7 @@ 1A69465B21916BE5000093A2 /* EthereumTxSender.swift */, 6E8BFC3021AFDBB5004752CC /* EthereumBalanceLoader.swift */, 89FE21D920EDB10900A09302 /* CustomERC20TokenService.swift */, + 1A19C2DD21B12DE800C289D0 /* EthereumMessageSigner.swift */, 89733043217ED041001B8D93 /* ETHSignMessageService.swift */, 89D84CE021475755006B0287 /* NFTService.swift */, 6E876840218022F30032EBCE /* TokenProfile.swift */, @@ -1397,6 +1400,7 @@ 6E83CB90216F573100029324 /* GuideCollectionViewCell.swift in Sources */, 1AEBF3E4219BA1E700653FF5 /* BigUInt+Extension.swift in Sources */, 1A6007A221A2AA9A00C7B712 /* PageItemAppearance.swift in Sources */, + 1A19C2DE21B12DE800C289D0 /* EthereumMessageSigner.swift in Sources */, 6E7D3842219C47940031177B /* TransactionHistoryPresenter.swift in Sources */, 6E9B947D21A4083100D67357 /* DesignableGradientView.swift in Sources */, 89B895B120F8B0CD00B9468B /* BrowserViewController.swift in Sources */, diff --git a/Neuron/Services/Ethereum/EthereumMessageSigner.swift b/Neuron/Services/Ethereum/EthereumMessageSigner.swift new file mode 100644 index 00000000..e319a859 --- /dev/null +++ b/Neuron/Services/Ethereum/EthereumMessageSigner.swift @@ -0,0 +1,59 @@ +// +// EthereumMessageSigner.swift +// Neuron +// +// Created by James Chen on 2018/11/30. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import Foundation +import secp256k1_swift + +/// Ethereum Message Signer +/// TODO: Web3swift should've already provided these. +struct EthereumMessageSigner { + enum SignError: Error { + case invalidPrivateKey + case invalidSignature + } + + func signPersonalMessage(message: Data, privateKey: String, useExtraEntropy: Bool = true) throws -> String? { + return try sign(message: appendPersonalMessagePrefix(for: message), privateKey: privateKey, useExtraEntropy: useExtraEntropy) + } + + func sign(message: Data, privateKey: String, useExtraEntropy: Bool = true) throws -> String? { + return try signHash(hashMessage(message), privateKey: privateKey, useExtraEntropy: useExtraEntropy).toHexString().addHexPrefix() + } + + func hashMessage(_ message: Data) -> Data { + return message.sha3(.keccak256) + } + + func hashPersonalMessage(_ personalMessage: Data) -> Data? { + return hashMessage(appendPersonalMessagePrefix(for: personalMessage)) + } +} + +private extension EthereumMessageSigner { + func signHash(_ hash: Data, privateKey: String, useExtraEntropy: Bool) throws -> Data { + guard let privateKeyData = Data.fromHex(privateKey) else { + throw SignError.invalidPrivateKey + } + let serializedSignature = SECP256K1.signForRecovery(hash: hash, privateKey: privateKeyData, useExtraEntropy: useExtraEntropy).serializedSignature + guard var signature = serializedSignature else { + throw SignError.invalidSignature + } + signature[64] += 27 + return signature + } + + func appendPersonalMessagePrefix(for message: Data) -> Data { + let prefix = "\u{19}Ethereum Signed Message:\n\(message.count)" + let prefixData = prefix.data(using: .ascii)! + if message.count >= prefixData.count && prefixData == message[0 ..< prefixData.count] { + return message + } else { + return prefixData + message + } + } +} diff --git a/Podfile b/Podfile index 870a45d1..7d41d65e 100644 --- a/Podfile +++ b/Podfile @@ -5,7 +5,7 @@ target 'Neuron' do use_frameworks! inhibit_all_warnings! - pod 'AppChainSwift', git: "https://github.com/cryptape/appchain-swift", tag: "v0.20.3" + pod 'AppChainSwift', git: "https://github.com/cryptape/appchain-swift", tag: "v0.20.4" pod 'web3swift', "~> 2.0.1" pod 'RealmSwift' diff --git a/Podfile.lock b/Podfile.lock index 860d94b7..d262f38f 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,6 +1,6 @@ PODS: - Alamofire (4.7.3) - - AppChainSwift (0.20.3): + - AppChainSwift (0.20.4): - BigInt (~> 3.1) - CryptoSwift (~> 0.13) - PromiseKit (~> 6.5) @@ -61,7 +61,7 @@ PODS: DEPENDENCIES: - Alamofire - - AppChainSwift (from `https://github.com/cryptape/appchain-swift`, tag `v0.20.3`) + - AppChainSwift (from `https://github.com/cryptape/appchain-swift`, tag `v0.20.4`) - BulletinBoard (from `https://github.com/alexaubry/BulletinBoard`, commit `7086607d3476cea29cd77a65d13df5c8ed0da52e`) - EFQRCode - IGIdenticon (~> 0.6) @@ -101,7 +101,7 @@ SPEC REPOS: EXTERNAL SOURCES: AppChainSwift: :git: https://github.com/cryptape/appchain-swift - :tag: v0.20.3 + :tag: v0.20.4 BulletinBoard: :commit: 7086607d3476cea29cd77a65d13df5c8ed0da52e :git: https://github.com/alexaubry/BulletinBoard @@ -112,7 +112,7 @@ EXTERNAL SOURCES: CHECKOUT OPTIONS: AppChainSwift: :git: https://github.com/cryptape/appchain-swift - :tag: v0.20.3 + :tag: v0.20.4 BulletinBoard: :commit: 7086607d3476cea29cd77a65d13df5c8ed0da52e :git: https://github.com/alexaubry/BulletinBoard @@ -122,7 +122,7 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: Alamofire: c7287b6e5d7da964a70935e5db17046b7fde6568 - AppChainSwift: cce29f74b3ef4cd73a694f4b30a6d02860573dea + AppChainSwift: 134bd8cd8db4bd2bd941de52314796894b5b6049 BigInt: 76b5dfdfa3e2e478d4ffdf161aeede5502e2742f BulletinBoard: 927f4f9fd22503ceecd71bfc830fd65d5996593a CryptoSwift: 16e78bebf567bad1c87b2d58f6547f25b74c31aa @@ -146,6 +146,6 @@ SPEC CHECKSUMS: Toast-Swift: 594b5c5e5129f15438e410207e43287f027b3c00 web3swift: 33f30ca0e061e0f89117dfb46f5ee9f626eff6d6 -PODFILE CHECKSUM: ba3c171e6bb54015e1b0de1d7316d9a495ae8673 +PODFILE CHECKSUM: d57a34aeb26d8dd7dffb28d602573d66208b399e COCOAPODS: 1.5.3 From da5f7556b339a568ed5cdfdd0b09dd29b2ffb105 Mon Sep 17 00:00:00 2001 From: James Chen Date: Fri, 30 Nov 2018 17:41:51 +0900 Subject: [PATCH 288/315] Insert Error namespace into ETHSignMessageService i18n --- Neuron/Services/Ethereum/ETHSignMessageService.swift | 2 +- Neuron/en.lproj/Localizable.strings | 4 ++-- Neuron/zh-Hans.lproj/Localizable.strings | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Neuron/Services/Ethereum/ETHSignMessageService.swift b/Neuron/Services/Ethereum/ETHSignMessageService.swift index 388b84d3..502e1d67 100644 --- a/Neuron/Services/Ethereum/ETHSignMessageService.swift +++ b/Neuron/Services/Ethereum/ETHSignMessageService.swift @@ -15,7 +15,7 @@ struct ETHSignMessageService { case signMessageFailed var errorDescription: String? { - return "ETHSignMessageService.\(rawValue)".localized() + return "ETHSignMessageService.Error.\(rawValue)".localized() } } diff --git a/Neuron/en.lproj/Localizable.strings b/Neuron/en.lproj/Localizable.strings index 27dc1e61..30a5220f 100644 --- a/Neuron/en.lproj/Localizable.strings +++ b/Neuron/en.lproj/Localizable.strings @@ -66,8 +66,8 @@ // Ethereum Sign Message Error // --------------------------------------------------------- -"ETHSignMessageService.walletNotFound" = "Wallet not found"; -"ETHSignMessageService.signMessageFailed" = "Failed to sign the message"; +"ETHSignMessageService.Error.walletNotFound" = "Wallet not found"; +"ETHSignMessageService.Error.signMessageFailed" = "Failed to sign the message"; // Wallet // --------------------------------------------------------- diff --git a/Neuron/zh-Hans.lproj/Localizable.strings b/Neuron/zh-Hans.lproj/Localizable.strings index 1e3c28bd..a9c9b611 100644 --- a/Neuron/zh-Hans.lproj/Localizable.strings +++ b/Neuron/zh-Hans.lproj/Localizable.strings @@ -65,8 +65,8 @@ // Ethereum Sign Message Error // --------------------------------------------------------- -"ETHSignMessageService.walletNotFound" = "未找到该钱包"; -"ETHSignMessageService.signMessageFailed" = "签名失败"; +"ETHSignMessageService.Error.walletNotFound" = "未找到该钱包"; +"ETHSignMessageService.Error.signMessageFailed" = "签名失败"; // Wallet // --------------------------------------------------------- From 40f329e81e3eceb3a66bc1e6acac94e30df6d13f Mon Sep 17 00:00:00 2001 From: James Chen Date: Fri, 30 Nov 2018 18:30:54 +0900 Subject: [PATCH 289/315] Polish message signing --- Neuron.xcodeproj/project.pbxproj | 4 -- .../Dapp/WebView/MessageSignController.swift | 53 ++++++++++++++++--- .../Ethereum/ETHSignMessageService.swift | 49 ----------------- Neuron/en.lproj/Localizable.strings | 6 +-- Neuron/zh-Hans.lproj/Localizable.strings | 6 +-- Podfile | 2 +- Podfile.lock | 12 ++--- 7 files changed, 59 insertions(+), 73 deletions(-) delete mode 100644 Neuron/Services/Ethereum/ETHSignMessageService.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 74e4c5e6..3a333ec6 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -130,7 +130,6 @@ 896D044220AE69C5002CFF6A /* NeuronTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896D044120AE69C5002CFF6A /* NeuronTests.swift */; }; 896D044D20AE69C5002CFF6A /* NeuronUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 896D044C20AE69C5002CFF6A /* NeuronUITests.swift */; }; 8972FD0F218B54A100C27147 /* AdvancedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8972FD0E218B54A100C27147 /* AdvancedViewController.swift */; }; - 89733044217ED041001B8D93 /* ETHSignMessageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89733043217ED041001B8D93 /* ETHSignMessageService.swift */; }; 8978846321AD26690034AF4F /* neuron.js in Resources */ = {isa = PBXBuildFile; fileRef = 8978846221AD26690034AF4F /* neuron.js */; }; 897AFC5B2130129500383A12 /* CurrencyViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897AFC592130129500383A12 /* CurrencyViewController.swift */; }; 8981212A218D3161000813C8 /* QRCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89812129218D3161000813C8 /* QRCodeViewController.swift */; }; @@ -351,7 +350,6 @@ 896D044C20AE69C5002CFF6A /* NeuronUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeuronUITests.swift; sourceTree = ""; }; 896D044E20AE69C5002CFF6A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 8972FD0E218B54A100C27147 /* AdvancedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedViewController.swift; sourceTree = ""; }; - 89733043217ED041001B8D93 /* ETHSignMessageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ETHSignMessageService.swift; sourceTree = ""; }; 8978846221AD26690034AF4F /* neuron.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = neuron.js; sourceTree = ""; }; 897AFC592130129500383A12 /* CurrencyViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrencyViewController.swift; sourceTree = ""; }; 89812129218D3161000813C8 /* QRCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeViewController.swift; sourceTree = ""; }; @@ -524,7 +522,6 @@ 6E8BFC3021AFDBB5004752CC /* EthereumBalanceLoader.swift */, 89FE21D920EDB10900A09302 /* CustomERC20TokenService.swift */, 1A19C2DD21B12DE800C289D0 /* EthereumMessageSigner.swift */, - 89733043217ED041001B8D93 /* ETHSignMessageService.swift */, 89D84CE021475755006B0287 /* NFTService.swift */, 6E876840218022F30032EBCE /* TokenProfile.swift */, 6E3B2B6C219ED9230095257D /* Ethereum+TransactionDetails.swift */, @@ -1387,7 +1384,6 @@ 892A1985215A55F400B2293D /* Double+Extension.swift in Sources */, 8964A19820C15EA20086848F /* ButtonTagUpView.swift in Sources */, 1A3C652C212EAC4800F1A0AA /* String+Extension.swift in Sources */, - 89733044217ED041001B8D93 /* ETHSignMessageService.swift in Sources */, 1A69465C21916BE5000093A2 /* EthereumTxSender.swift in Sources */, 898889CA2136528B00D04AA8 /* PrivatekeyViewController.swift in Sources */, 1A1E1B8B21A3CF3A00C24D10 /* SignMessagePageItem.swift in Sources */, diff --git a/Neuron/Sections/Dapp/WebView/MessageSignController.swift b/Neuron/Sections/Dapp/WebView/MessageSignController.swift index e98c6867..72523a21 100644 --- a/Neuron/Sections/Dapp/WebView/MessageSignController.swift +++ b/Neuron/Sections/Dapp/WebView/MessageSignController.swift @@ -8,6 +8,7 @@ import UIKit import BLTNBoard +import AppChain protocol MessageSignControllerDelegate: class { func messageSignCallBackWebView(id: Int, value: String, error: DAppError?) @@ -118,8 +119,6 @@ private extension MessageSignController { switch dappCommonModel.name { case .signMessage: appChainSignMessage(password: password) - case .signPersonalMessage: - appChainSignPersonalMessage(password: password) default: break } @@ -128,7 +127,11 @@ private extension MessageSignController { func ethSignPersonalMessage(password: String) { DispatchQueue.global().async { do { - let signed = try ETHSignMessageService.signPersonal(message: self.dappCommonModel.eth?.data ?? "", password: password) + let messageData = Data.fromHex(self.dappCommonModel.eth?.data ?? "") ?? Data() + let privateKey = try self.getPrivateKey(password: password) + guard let signed = try EthereumMessageSigner().signPersonalMessage(message: messageData, privateKey: privateKey) else { + throw Error.signMessageFailed + } DispatchQueue.main.async { Toast.hideHUD() self.finish(signed: signed) @@ -145,7 +148,11 @@ private extension MessageSignController { func ethSignMessage(password: String) { DispatchQueue.global().async { do { - let signed = try ETHSignMessageService.sign(message: self.dappCommonModel.eth?.data ?? "", password: password) + let messageData = Data.fromHex(self.dappCommonModel.eth?.data ?? "") ?? Data() + let privateKey = try self.getPrivateKey(password: password) + guard let signed = try EthereumMessageSigner().sign(message: messageData, privateKey: privateKey) else { + throw Error.signMessageFailed + } DispatchQueue.main.async { Toast.hideHUD() self.finish(signed: signed) @@ -160,10 +167,42 @@ private extension MessageSignController { } func appChainSignMessage(password: String) { - // TODO: connect this + DispatchQueue.global().async { + do { + let messageData = Data.fromHex(self.dappCommonModel.eth?.data ?? "") ?? Data() + let privateKey = try self.getPrivateKey(password: password) + guard let signed = try MessageSigner().sign(message: messageData, privateKey: privateKey) else { + throw Error.signMessageFailed + } + DispatchQueue.main.async { + Toast.hideHUD() + self.finish(signed: signed) + } + } catch let error { + DispatchQueue.main.async { + Toast.hideHUD() + self.showSignError(error.localizedDescription) + } + } + } + } +} + +extension MessageSignController { + enum Error: String, LocalizedError { + case walletNotFound + case signMessageFailed + + var errorDescription: String? { + return "MessageSign.Error.\(rawValue)".localized() + } } - func appChainSignPersonalMessage(password: String) { - // TODO: connect this + private func getPrivateKey(password: String) throws -> String { + guard let wallet = AppModel.current.currentWallet!.wallet else { + throw Error.walletNotFound + } + + return try WalletManager.default.exportPrivateKey(wallet: wallet, password: password) } } diff --git a/Neuron/Services/Ethereum/ETHSignMessageService.swift b/Neuron/Services/Ethereum/ETHSignMessageService.swift deleted file mode 100644 index 502e1d67..00000000 --- a/Neuron/Services/Ethereum/ETHSignMessageService.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// ETHSignMessageService.swift -// Neuron -// -// Created by XiaoLu on 2018/10/23. -// Copyright © 2018 Cryptape. All rights reserved. -// - -import Foundation -import AppChain - -struct ETHSignMessageService { - enum Error: String, LocalizedError { - case walletNotFound - case signMessageFailed - - var errorDescription: String? { - return "ETHSignMessageService.Error.\(rawValue)".localized() - } - } - - public static func sign(message: String, password: String) throws -> String { - let messageData = Data.fromHex(message) ?? Data() - let walletModel = AppModel.current.currentWallet! - guard let wallet = walletModel.wallet else { - throw Error.walletNotFound - } - - let privateKey = try WalletManager.default.exportPrivateKey(wallet: wallet, password: password) - guard let signed = try EthereumMessageSigner().sign(message: messageData, privateKey: privateKey) else { - throw Error.signMessageFailed - } - return signed - } - - public static func signPersonal(message: String, password: String) throws -> String { - let messageData = Data.fromHex(message) ?? Data() - let walletModel = AppModel.current.currentWallet! - guard let wallet = walletModel.wallet else { - throw Error.walletNotFound - } - - let privateKey = try WalletManager.default.exportPrivateKey(wallet: wallet, password: password) - guard let signed = try EthereumMessageSigner().signPersonalMessage(message: messageData, privateKey: privateKey) else { - throw Error.signMessageFailed - } - return signed - } -} diff --git a/Neuron/en.lproj/Localizable.strings b/Neuron/en.lproj/Localizable.strings index 30a5220f..daa3120c 100644 --- a/Neuron/en.lproj/Localizable.strings +++ b/Neuron/en.lproj/Localizable.strings @@ -64,10 +64,10 @@ "CustomTokenError.badSymbolError" = "Bad token symbol"; "CustomTokenError.undefinedError" = "Undefined error"; -// Ethereum Sign Message Error +// Sign Message Error // --------------------------------------------------------- -"ETHSignMessageService.Error.walletNotFound" = "Wallet not found"; -"ETHSignMessageService.Error.signMessageFailed" = "Failed to sign the message"; +"MessageSign.Error.walletNotFound" = "Wallet not found"; +"MessageSign.Error.signMessageFailed" = "Failed to sign the message"; // Wallet // --------------------------------------------------------- diff --git a/Neuron/zh-Hans.lproj/Localizable.strings b/Neuron/zh-Hans.lproj/Localizable.strings index a9c9b611..1226f779 100644 --- a/Neuron/zh-Hans.lproj/Localizable.strings +++ b/Neuron/zh-Hans.lproj/Localizable.strings @@ -63,10 +63,10 @@ "CustomTokenError.badSymbolError" = "Token symbol不正确"; "CustomTokenError.undefinedError" = "未知错误"; -// Ethereum Sign Message Error +// Sign Message Error // --------------------------------------------------------- -"ETHSignMessageService.Error.walletNotFound" = "未找到该钱包"; -"ETHSignMessageService.Error.signMessageFailed" = "签名失败"; +"MessageSign.Error.walletNotFound" = "未找到该钱包"; +"MessageSign.Error.signMessageFailed" = "签名失败"; // Wallet // --------------------------------------------------------- diff --git a/Podfile b/Podfile index 7d41d65e..363c6242 100644 --- a/Podfile +++ b/Podfile @@ -5,7 +5,7 @@ target 'Neuron' do use_frameworks! inhibit_all_warnings! - pod 'AppChainSwift', git: "https://github.com/cryptape/appchain-swift", tag: "v0.20.4" + pod 'AppChainSwift', git: "https://github.com/cryptape/appchain-swift", tag: "v0.20.5" pod 'web3swift', "~> 2.0.1" pod 'RealmSwift' diff --git a/Podfile.lock b/Podfile.lock index d262f38f..f7e7f4c1 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,6 +1,6 @@ PODS: - Alamofire (4.7.3) - - AppChainSwift (0.20.4): + - AppChainSwift (0.20.5): - BigInt (~> 3.1) - CryptoSwift (~> 0.13) - PromiseKit (~> 6.5) @@ -61,7 +61,7 @@ PODS: DEPENDENCIES: - Alamofire - - AppChainSwift (from `https://github.com/cryptape/appchain-swift`, tag `v0.20.4`) + - AppChainSwift (from `https://github.com/cryptape/appchain-swift`, tag `v0.20.5`) - BulletinBoard (from `https://github.com/alexaubry/BulletinBoard`, commit `7086607d3476cea29cd77a65d13df5c8ed0da52e`) - EFQRCode - IGIdenticon (~> 0.6) @@ -101,7 +101,7 @@ SPEC REPOS: EXTERNAL SOURCES: AppChainSwift: :git: https://github.com/cryptape/appchain-swift - :tag: v0.20.4 + :tag: v0.20.5 BulletinBoard: :commit: 7086607d3476cea29cd77a65d13df5c8ed0da52e :git: https://github.com/alexaubry/BulletinBoard @@ -112,7 +112,7 @@ EXTERNAL SOURCES: CHECKOUT OPTIONS: AppChainSwift: :git: https://github.com/cryptape/appchain-swift - :tag: v0.20.4 + :tag: v0.20.5 BulletinBoard: :commit: 7086607d3476cea29cd77a65d13df5c8ed0da52e :git: https://github.com/alexaubry/BulletinBoard @@ -122,7 +122,7 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: Alamofire: c7287b6e5d7da964a70935e5db17046b7fde6568 - AppChainSwift: 134bd8cd8db4bd2bd941de52314796894b5b6049 + AppChainSwift: 0957a762aa81d31cee3276834096e0f6d4fca1fd BigInt: 76b5dfdfa3e2e478d4ffdf161aeede5502e2742f BulletinBoard: 927f4f9fd22503ceecd71bfc830fd65d5996593a CryptoSwift: 16e78bebf567bad1c87b2d58f6547f25b74c31aa @@ -146,6 +146,6 @@ SPEC CHECKSUMS: Toast-Swift: 594b5c5e5129f15438e410207e43287f027b3c00 web3swift: 33f30ca0e061e0f89117dfb46f5ee9f626eff6d6 -PODFILE CHECKSUM: d57a34aeb26d8dd7dffb28d602573d66208b399e +PODFILE CHECKSUM: beb75d52d4fb090be32eb898fb3ca2ea9a3e4566 COCOAPODS: 1.5.3 From 3a5f892d714c42ba1efb5b9930b2161653c993ba Mon Sep 17 00:00:00 2001 From: LuFP Date: Fri, 30 Nov 2018 18:48:02 +0800 Subject: [PATCH 290/315] Modify gas limit gas price and value check condition --- Neuron/Sections/Dapp/WebView/BrowserViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift index 166d1a2e..be1e1778 100644 --- a/Neuron/Sections/Dapp/WebView/BrowserViewController.swift +++ b/Neuron/Sections/Dapp/WebView/BrowserViewController.swift @@ -286,15 +286,15 @@ extension BrowserViewController { } return true } else { - if dappModel.eth?.value?.toBigUInt() == nil { + if dappModel.eth?.value?.toBigUInt() == nil && dappModel.eth?.value != nil { Toast.showToast(text: "DApp.SendTransactionError.emptyValue".localized()) return false } - if dappModel.eth?.gasLimit?.toBigUInt() == nil { + if dappModel.eth?.gasLimit?.toBigUInt() == nil && dappModel.eth?.gasLimit != nil { Toast.showToast(text: "DApp.SendTransactionError.emptyGasLimit".localized()) return false } - if dappModel.eth?.gasPrice?.toBigUInt() == nil { + if dappModel.eth?.gasPrice?.toBigUInt() == nil && dappModel.eth?.gasPrice != nil { Toast.showToast(text: "DApp.SendTransactionError.emptyGasPrice".localized()) return false } From 23e918f6cfec8ee40745e06f5bd71428ee022420 Mon Sep 17 00:00:00 2001 From: LuFP Date: Fri, 30 Nov 2018 18:49:07 +0800 Subject: [PATCH 291/315] Fix eth DApp transaction gas limit not enough --- Neuron/Sections/Dapp/WebView/ContractController.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Neuron/Sections/Dapp/WebView/ContractController.swift b/Neuron/Sections/Dapp/WebView/ContractController.swift index 5d019cff..aa7e38ab 100644 --- a/Neuron/Sections/Dapp/WebView/ContractController.swift +++ b/Neuron/Sections/Dapp/WebView/ContractController.swift @@ -186,8 +186,8 @@ class ContractController: UITableViewController, TransactonSender { options.from = EthereumAddress(self.dappCommonModel.eth?.from ?? "") options.value = self.dappCommonModel.eth?.value?.toBigUInt() let contract = web3.contract(Web3.Utils.coldWalletABI, at: EthereumAddress(self.dappCommonModel.eth?.to ?? ""))! - if let estimatedGas = try? contract.method(transactionOptions: options)!.estimateGas(transactionOptions: nil) { - self.gasLimit = estimatedGas + if let estimatedGas = try? contract.method(transactionOptions: options)!.estimateGas(transactionOptions: options) { + self.gasLimit = estimatedGas * 4 } else { self.gasLimit = BigUInt(0) } @@ -196,8 +196,7 @@ class ContractController: UITableViewController, TransactonSender { let gas = self.gasPrice * self.gasLimit self.ethereumGas = Web3Utils.formatToEthereumUnits(gas, toUnits: .eth, decimals: 8, fallbackToScientific: false) self.gasLabel.text = Double(self.ethereumGas!)!.trailingZerosTrimmed + self.tokenModel.symbol - let bigUIntValue = Web3Utils.parseToBigUInt(self.value, units: .eth)! - self.totlePayLabel.text = self.getTotleValue(value: bigUIntValue.fromQuota(), gas: gas) + self.tokenModel.symbol + self.totlePayLabel.text = self.getTotleValue(value: self.dappCommonModel.eth?.value?.toBigUInt() ?? 0, gas: gas) + self.tokenModel.symbol Toast.hideHUD() } } From 380c579f0983567a87aafd0e175aee5c009f3b4d Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 10:40:56 +0800 Subject: [PATCH 292/315] Modify password check --- .../Wallet/AddWallet/CreateWalletController.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Neuron/Sections/Wallet/AddWallet/CreateWalletController.swift b/Neuron/Sections/Wallet/AddWallet/CreateWalletController.swift index 18b1df4b..b81d9b99 100644 --- a/Neuron/Sections/Wallet/AddWallet/CreateWalletController.swift +++ b/Neuron/Sections/Wallet/AddWallet/CreateWalletController.swift @@ -73,6 +73,10 @@ class CreateWalletController: UITableViewController { } func canProceedNextStep() -> Bool { + if password != confirmPassword { + Toast.showToast(text: "两次密码不一致") + return false + } if case .invalid(let reason) = WalletNameValidator.validate(walletName: name ?? "") { Toast.showToast(text: reason) return false @@ -81,11 +85,6 @@ class CreateWalletController: UITableViewController { Toast.showToast(text: reason) return false } - if password != confirmPassword { - Toast.showToast(text: "两次密码不一致") - return false - } - return true } From 440b9edbeb247222bfe1d52422e15e0c4559cfa5 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 10:41:45 +0800 Subject: [PATCH 293/315] Update send transaction --- .../Common/PageItems/TxSummaryPageItem.swift | 7 ++++++- .../SendTransactionViewController.swift | 17 +++++++++++++++++ .../Transaction/TradeDetailsController.swift | 2 +- .../TransactionHistoryPresenter.swift | 15 ++++++++++++--- .../Transaction/TransactionParamBuilder.swift | 6 +++++- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift b/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift index f348c890..eb695884 100644 --- a/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift +++ b/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift @@ -21,7 +21,12 @@ class TxSummaryPageItem: BLTNPageItem { } func update(_ param: TransactionParamBuilder) { - summaryView.amountLabel.text = Double.fromAmount(param.value, decimals: param.decimals).decimal + " " + if param.amount >= 0.00000001 { + summaryView.amountLabel.text = Double.fromAmount(param.value, decimals: param.decimals).decimal + " " + } else { + summaryView.amountLabel.text = String(param.amount) + " " + } + let range = NSRange(location: summaryView.amountLabel.text!.lengthOfBytes(using: .utf8), length: param.symbol.lengthOfBytes(using: .utf8)) summaryView.amountLabel.text! += param.symbol let attributedText = NSMutableAttributedString(attributedString: summaryView.amountLabel.attributedText!) diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index 70e3b029..7ac30faa 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -139,6 +139,7 @@ class SendTransactionViewController: UITableViewController, TransactonSender { let amount = Double(amountTextField.text!) ?? 0.0 paramBuilder.value = amount.toAmount(token.decimals) paramBuilder.to = addressTextField.text! + paramBuilder.amount = amount if isEffectiveTransferInfo { summaryPageItem.update(paramBuilder) @@ -179,6 +180,10 @@ class SendTransactionViewController: UITableViewController, TransactonSender { private func updateGasCost() { gasCostLabel.text = "\(paramBuilder.txFeeNatural.decimal) \(paramBuilder.nativeCoinSymbol)" + if paramBuilder.tokenPrice > 0 { + let amount = paramBuilder.txFeeNatural * paramBuilder.tokenPrice + gasCostLabel.text! += "≈ \(paramBuilder.currencySymbol) " + String(format: "%.2lf", amount) + } } private func createPasswordPageItem() -> PasswordPageItem { @@ -331,10 +336,19 @@ extension SendTransactionViewController: TransactionSwitchTokenViewControllerDel } else { cell.isHidden = false } + + if indexPath.row == 3 { + if paramBuilder.tokenType == .appChain { + cell.accessoryType = .none + } else { + cell.accessoryType = .disclosureIndicator + } + } } func switchToken(switchToken: TransactionSwitchTokenViewController, didSwitchToToken token: TokenModel) { self.token = token + tableView.reloadData() observers.forEach { (observe) in observe.invalidate() @@ -349,6 +363,9 @@ extension SendTransactionViewController: TransactionSwitchTokenViewControllerDel observers.append(paramBuilder.observe(\.txFeeNatural, options: [.initial]) { (_, _) in self.updateGasCost() }) + observers.append(paramBuilder.observe(\.tokenPrice, options: [.initial]) { [weak self](_, _) in + self?.updateGasCost() + }) paramBuilder.from = AppModel.current.currentWallet!.address if recipientAddress != nil { paramBuilder.to = recipientAddress diff --git a/Neuron/Sections/Transaction/TradeDetailsController.swift b/Neuron/Sections/Transaction/TradeDetailsController.swift index 0a8ca971..edb16b1d 100644 --- a/Neuron/Sections/Transaction/TradeDetailsController.swift +++ b/Neuron/Sections/Transaction/TradeDetailsController.swift @@ -33,7 +33,7 @@ class TradeDetailsController: UIViewController, UITableViewDataSource, UITableVi ] } else if let appChain = transaction as? AppChainTransactionDetails { transactionType = "AppChain" - let gasUsed = String(Double.fromAmount(appChain.gasUsed, decimals: appChain.token.decimals)) + let gasUsed = String(Double.fromAmount(appChain.gasUsed, decimals: 9)) titleArr = ["区块链网络", "接受方", "发送方", "手续费", "交易流水号", "所在区块", "入块时间"] subBtnArr = [ appChain.chainName, diff --git a/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift b/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift index ceaf8709..e87fb70a 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift @@ -80,18 +80,26 @@ class TransactionHistoryPresenter: NSObject, TransactionStatusManagerDelegate { // MARK: - Merge private func mergeSentTransactions(from list: [TransactionDetails]) -> [TransactionDetails] { + // + var newList = [TransactionDetails]() + list.forEach { (trans) in + if !newList.contains(where: { $0.hash == trans.hash }) { + newList.append(trans) + } + } + // Date range let maxDate: Date = self.transactions.first?.date ?? Date.distantFuture let minDate: Date if self.hasMoreData { - minDate = list.last?.date ?? Date.distantPast + minDate = newList.last?.date ?? Date.distantPast } else { minDate = Date.distantPast } let sentTransactions = self.sentTransactions let sentList = sentTransactions.filter({ (sentTransaction) -> Bool in // merge status - if let transaction = list.first(where: { $0.hash == sentTransaction.hash }) { + if let transaction = newList.first(where: { $0.hash == sentTransaction.hash }) { self.sentTransactions.removeAll(where: { $0.hash == sentTransaction.hash }) transaction.status = sentTransaction.status return false @@ -103,7 +111,7 @@ class TransactionHistoryPresenter: NSObject, TransactionStatusManagerDelegate { return false } }) - return (list + sentList).sorted(by: { $0.date > $1.date }) + return (newList + sentList).sorted(by: { $0.date > $1.date }) } // MARK: - Load transactions @@ -143,6 +151,7 @@ class TransactionHistoryPresenter: NSObject, TransactionStatusManagerDelegate { func sentTransactionStatusChanged(transaction: TransactionDetails) { if let idx = transactions.firstIndex(where: { $0.hash == transaction.hash }) { DispatchQueue.main.async { + transaction.token = self.token self.transactions.remove(at: idx) self.transactions.insert(transaction, at: idx) self.delegate?.updateTransactions(transaction: self.transactions, updates: [idx], error: nil) diff --git a/Neuron/Sections/Transaction/TransactionParamBuilder.swift b/Neuron/Sections/Transaction/TransactionParamBuilder.swift index 29de0665..927f5093 100644 --- a/Neuron/Sections/Transaction/TransactionParamBuilder.swift +++ b/Neuron/Sections/Transaction/TransactionParamBuilder.swift @@ -19,6 +19,7 @@ class TransactionParamBuilder: NSObject { var data = Data() var contractAddress = "" var chainId = "" + var amount = 0.0 var fetchedGasPrice: BigUInt = 1 // Fetched from node as recommended gas price var gasPrice: BigUInt = 1 { @@ -145,9 +146,12 @@ class TransactionParamBuilder: NSObject { private func fetchTokenPrice(token: TokenModel) { let currency = LocalCurrencyService.shared.getLocalCurrencySelect() let symbol = token.symbol + currencySymbol = currency.symbol DispatchQueue.global().async { if let price = TokenPriceLoader().getPrice(symbol: symbol, currency: currency.short) { - self.tokenPrice = price + DispatchQueue.main.async { + self.tokenPrice = price + } } } } From 392e47efe714be5be95bc392ebe0aa40510d3f40 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 10:45:20 +0800 Subject: [PATCH 294/315] Remove blank line --- Neuron/Sections/Transaction/TransactionHistoryPresenter.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift b/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift index e87fb70a..7343175a 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift @@ -80,7 +80,6 @@ class TransactionHistoryPresenter: NSObject, TransactionStatusManagerDelegate { // MARK: - Merge private func mergeSentTransactions(from list: [TransactionDetails]) -> [TransactionDetails] { - // var newList = [TransactionDetails]() list.forEach { (trans) in if !newList.contains(where: { $0.hash == trans.hash }) { From eb9bbf5853b71bde90f3d4303ea596b348a44bfb Mon Sep 17 00:00:00 2001 From: LuFP Date: Mon, 3 Dec 2018 11:26:26 +0800 Subject: [PATCH 295/315] =?UTF-8?q?Support=20transaction=20address=20has?= =?UTF-8?q?=20no=20=E2=80=980x=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Neuron/Services/AppChain/AppChainTxSender.swift | 2 +- Neuron/Services/Ethereum/EthereumTxSender.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Neuron/Services/AppChain/AppChainTxSender.swift b/Neuron/Services/AppChain/AppChainTxSender.swift index 25103645..76900b19 100644 --- a/Neuron/Services/AppChain/AppChainTxSender.swift +++ b/Neuron/Services/AppChain/AppChainTxSender.swift @@ -32,7 +32,7 @@ class AppChainTxSender { chainId: BigUInt, password: String ) throws -> TxHash { - let destinationEthAddress = Address(to) + let destinationEthAddress = Address(to.addHexPrefix()) if !to.isEmpty && destinationEthAddress == nil { throw SendTransactionError.invalidDestinationAddress } diff --git a/Neuron/Services/Ethereum/EthereumTxSender.swift b/Neuron/Services/Ethereum/EthereumTxSender.swift index 2b1e52e4..e44812ff 100644 --- a/Neuron/Services/Ethereum/EthereumTxSender.swift +++ b/Neuron/Services/Ethereum/EthereumTxSender.swift @@ -32,7 +32,7 @@ class EthereumTxSender { data: Data, password: String ) throws -> TxHash { - guard let toAddress = EthereumAddress(to) else { + guard let toAddress = EthereumAddress(to.addHexPrefix()) else { throw SendTransactionError.invalidDestinationAddress } From f0ce310f21634ec832e0a6da2248729b00b8f74a Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 11:35:31 +0800 Subject: [PATCH 296/315] Fix erc20 tx free bug --- .../SendTransactionViewController.swift | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index 70e3b029..498010af 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -12,6 +12,7 @@ import BigInt import AppChain import Web3swift import EthereumAddress +import RealmSwift protocol TransactonSender { var paramBuilder: TransactionParamBuilder! { get set } @@ -140,6 +141,15 @@ class SendTransactionViewController: UITableViewController, TransactonSender { paramBuilder.value = amount.toAmount(token.decimals) paramBuilder.to = addressTextField.text! + if token.type == .erc20 { + let realm = try! Realm() + let ether = realm.objects(TokenModel.self).first(where: { $0.type == .ether })! + if ether.tokenBalance >= paramBuilder.txFeeNatural { + Toast.showToast(text: "请确保账户剩余\(token.gasSymbol)高于矿工费用,以便顺利完成转账~") + return + } + } + if isEffectiveTransferInfo { summaryPageItem.update(paramBuilder) bulletinManager.showBulletin(above: self) @@ -155,12 +165,27 @@ class SendTransactionViewController: UITableViewController, TransactonSender { @IBAction func transactionAvailableBalance() { // TODO: FIXME: erc20 token requires ETH balance for tx fee - let amount = token.tokenBalance - paramBuilder.txFeeNatural - amountTextField.text = "\(amount)" - paramBuilder.value = amount.toAmount(token.decimals) - guard paramBuilder.hasSufficientBalance else { - Toast.showToast(text: "请确保账户剩余\(token.gasSymbol)高于矿工费用,以便顺利完成转账~") - return + switch token.type { + case .ether, .appChain: + let amount = token.tokenBalance - paramBuilder.txFeeNatural + if amount < 0 { + Toast.showToast(text: "请确保账户剩余\(token.gasSymbol)高于矿工费用,以便顺利完成转账~") + return + } + amountTextField.text = "\(amount)" + paramBuilder.value = amount.toAmount(token.decimals) + case .erc20: + let realm = try! Realm() + let ether = realm.objects(TokenModel.self).first(where: { $0.type == .ether })! + if ether.tokenBalance < paramBuilder.txFeeNatural { + Toast.showToast(text: "请确保账户剩余\(token.gasSymbol)高于矿工费用,以便顺利完成转账~") + return + } + let amount = token.tokenBalance + amountTextField.text = "\(amount)" + paramBuilder.value = amount.toAmount(token.decimals) + default: + break } } From dbbb9c2cf4ff0879d13b0c41095925c2d79029ed Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 11:45:15 +0800 Subject: [PATCH 297/315] Fix MBA tx history --- Neuron/Models/Realm/RealmConfigurator.swift | 2 +- Neuron/Services/AppChain/AppChainTxSender.swift | 2 +- Neuron/Services/Common/SentTransaction.swift | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Neuron/Models/Realm/RealmConfigurator.swift b/Neuron/Models/Realm/RealmConfigurator.swift index b5f1e17d..6229021f 100644 --- a/Neuron/Models/Realm/RealmConfigurator.swift +++ b/Neuron/Models/Realm/RealmConfigurator.swift @@ -10,7 +10,7 @@ import Foundation import RealmSwift class RealmConfigurator { - private static var schemaVersion: UInt64 = 6 + private static var schemaVersion: UInt64 = 7 static func configure() { var config = Realm.Configuration() diff --git a/Neuron/Services/AppChain/AppChainTxSender.swift b/Neuron/Services/AppChain/AppChainTxSender.swift index 25103645..0b2f9a81 100644 --- a/Neuron/Services/AppChain/AppChainTxSender.swift +++ b/Neuron/Services/AppChain/AppChainTxSender.swift @@ -59,7 +59,7 @@ class AppChainTxSender { ) let signed = try sign(transaction: transaction, password: password) let txHash = try appChain.rpc.sendRawTransaction(signedTx: signed) - let sentTransaction = SentTransaction(tokenType: .appChain, from: from.address, hash: txHash, transaction: transaction) + let sentTransaction = SentTransaction(tokenType: .appChain, from: from.address, hash: txHash, transaction: transaction, chainHosts: appChain.provider.url.absoluteString) TransactionStatusManager.manager.insertTransaction(transaction: sentTransaction) return txHash } diff --git a/Neuron/Services/Common/SentTransaction.swift b/Neuron/Services/Common/SentTransaction.swift index b31c7c9c..bb7b4883 100644 --- a/Neuron/Services/Common/SentTransaction.swift +++ b/Neuron/Services/Common/SentTransaction.swift @@ -108,6 +108,7 @@ class SentTransaction: Object, ThreadSafeObject { @objc dynamic private var privateAmount: String = "" @objc dynamic private var privateTxFee: String = "" @objc dynamic private var privateTokenType: String = "" + @objc dynamic private var chainHosts: String! @objc override class func primaryKey() -> String? { return "txHash" } @@ -143,7 +144,7 @@ class SentTransaction: Object, ThreadSafeObject { } // AppChain - required convenience init(tokenType: TokenType, from: String, hash: String, transaction: Transaction) { + required convenience init(tokenType: TokenType, from: String, hash: String, transaction: Transaction, chainHosts: String) { self.init() txHash = hash blockNumber = BigUInt(transaction.validUntilBlock) @@ -152,6 +153,7 @@ class SentTransaction: Object, ThreadSafeObject { amount = transaction.value txFee = BigUInt(transaction.quota) status = .pending + self.chainHosts = chainHosts } // AppChainErc20 From e6f4afad36c0759b74b60eeac601f5e53c2b639a Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 12:03:06 +0800 Subject: [PATCH 298/315] Fix MBA tx history --- .../Transaction/TransactionHistoryPresenter.swift | 2 +- Neuron/Services/Common/SentTransaction.swift | 3 ++- Neuron/Services/Common/TransactionStatusManager.swift | 11 ++++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift b/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift index 7343175a..4daa9147 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryPresenter.swift @@ -49,7 +49,7 @@ class TransactionHistoryPresenter: NSObject, TransactionStatusManagerDelegate { self.loading = false if self.page == 1 { self.transactions = [] - self.sentTransactions = TransactionStatusManager.manager.getTransactions(walletAddress: self.token.walletAddress, tokenType: self.token.type, tokenAddress: self.token.address) + self.sentTransactions = TransactionStatusManager.manager.getTransactions(walletAddress: self.token.walletAddress, tokenType: self.token.type, tokenAddress: self.token.address, chainHosts: self.token.chainHosts) } self.page += 1 diff --git a/Neuron/Services/Common/SentTransaction.swift b/Neuron/Services/Common/SentTransaction.swift index bb7b4883..f8b93ccf 100644 --- a/Neuron/Services/Common/SentTransaction.swift +++ b/Neuron/Services/Common/SentTransaction.swift @@ -102,13 +102,14 @@ class SentTransaction: Object, ThreadSafeObject { } @objc dynamic var date: Date = Date() @objc dynamic var ethereumNetwork: String = "" + @objc dynamic var chainHosts: String! @objc dynamic private var privateBlockNumber: String = "" @objc dynamic private var privateStatus: Int = 0 @objc dynamic private var privateAmount: String = "" @objc dynamic private var privateTxFee: String = "" @objc dynamic private var privateTokenType: String = "" - @objc dynamic private var chainHosts: String! + @objc override class func primaryKey() -> String? { return "txHash" } diff --git a/Neuron/Services/Common/TransactionStatusManager.swift b/Neuron/Services/Common/TransactionStatusManager.swift index 9b85772b..c67ad106 100644 --- a/Neuron/Services/Common/TransactionStatusManager.swift +++ b/Neuron/Services/Common/TransactionStatusManager.swift @@ -65,14 +65,15 @@ class TransactionStatusManager: NSObject { } } - // MARK: - - func getTransactions(walletAddress: String, tokenType: TokenType, tokenAddress: String) -> [TransactionDetails] { + // MARK: - + func getTransactions(walletAddress: String, tokenType: TokenType, tokenAddress: String, chainHosts: String) -> [TransactionDetails] { let ethereumNetwork = EthereumNetwork().host().absoluteString return self.realm.objects(SentTransaction.self).filter({ + $0.chainHosts == chainHosts && $0.from == walletAddress && - $0.tokenType == tokenType && - $0.contractAddress == tokenAddress && - ($0.ethereumNetwork == "" || $0.ethereumNetwork == ethereumNetwork) + $0.tokenType == tokenType && + $0.contractAddress == tokenAddress && + ($0.ethereumNetwork == "" || $0.ethereumNetwork == ethereumNetwork) }).map({ $0.transactionDetails() }) } From dc5f756fc1be7649e6bc8150807c6cc15202614e Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 3 Dec 2018 13:26:07 +0900 Subject: [PATCH 299/315] Delete NSObject extension --- Neuron.xcodeproj/project.pbxproj | 4 ---- .../Foundation/NSObject+Extension.swift | 17 ----------------- 2 files changed, 21 deletions(-) delete mode 100644 Neuron/Extensions/Foundation/NSObject+Extension.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 3a333ec6..9338ad72 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -82,7 +82,6 @@ 6E9D7CBE216B386F0044176D /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9D7CBC216B386F0044176D /* AuthenticationViewController.swift */; }; 6E9D7CC1216B3AE40044176D /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6E9D7CC0216B3AE40044176D /* Authentication.storyboard */; }; 6E9D7CC5216B490D0044176D /* AuthDeviceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9D7CC4216B490D0044176D /* AuthDeviceViewController.swift */; }; - 6E9F9D58219AAF78009ED8B2 /* NSObject+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9F9D57219AAF78009ED8B2 /* NSObject+Extension.swift */; }; 6EABFB0B21991BC200305ED5 /* DAppQRCodeMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EABFB0A21991BC200305ED5 /* DAppQRCodeMessageHandler.swift */; }; 6EABFB0D21991CB300305ED5 /* DAppNativeMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EABFB0C21991CB300305ED5 /* DAppNativeMessageHandler.swift */; }; 6EABFB10219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EABFB0F219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift */; }; @@ -294,7 +293,6 @@ 6E9D7CBC216B386F0044176D /* AuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = ""; }; 6E9D7CC0216B3AE40044176D /* Authentication.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Authentication.storyboard; sourceTree = ""; }; 6E9D7CC4216B490D0044176D /* AuthDeviceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthDeviceViewController.swift; sourceTree = ""; }; - 6E9F9D57219AAF78009ED8B2 /* NSObject+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSObject+Extension.swift"; sourceTree = ""; }; 6EABFB0A21991BC200305ED5 /* DAppQRCodeMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppQRCodeMessageHandler.swift; sourceTree = ""; }; 6EABFB0C21991CB300305ED5 /* DAppNativeMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppNativeMessageHandler.swift; sourceTree = ""; }; 6EABFB0F219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppDeviceMotionMessageHandler.swift; sourceTree = ""; }; @@ -549,7 +547,6 @@ 6E876844218031670032EBCE /* UInt+Extension.swift */, 892A1984215A55F400B2293D /* Double+Extension.swift */, 1A3C652B212EAC4800F1A0AA /* String+Extension.swift */, - 6E9F9D57219AAF78009ED8B2 /* NSObject+Extension.swift */, ); path = Foundation; sourceTree = ""; @@ -1434,7 +1431,6 @@ 6E867E0D216C92A900BD6FE5 /* OverlayPresentable.swift in Sources */, 89D84CE121475755006B0287 /* NFTService.swift in Sources */, 898A1A2A20B7F0FC00ECB465 /* TradeDetailsController.swift in Sources */, - 6E9F9D58219AAF78009ED8B2 /* NSObject+Extension.swift in Sources */, 89A196192175D9C800BD5FA4 /* DAppDataHandle.swift in Sources */, 89D84CEB2148C03B006B0287 /* NFTHeaderReusableView.swift in Sources */, 8964A17D20BE7F750086848F /* MessageSignController.swift in Sources */, diff --git a/Neuron/Extensions/Foundation/NSObject+Extension.swift b/Neuron/Extensions/Foundation/NSObject+Extension.swift deleted file mode 100644 index 418f71f8..00000000 --- a/Neuron/Extensions/Foundation/NSObject+Extension.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// NSObject+Extension.swift -// Neuron -// -// Created by 晨风 on 2018/11/13. -// Copyright © 2018 Cryptape. All rights reserved. -// - -import Foundation - -extension NSObject { - static func swizzedMethod(originalSelector: Selector, swizzledSelector: Selector) { - guard let originalMethod = class_getInstanceMethod(self.classForCoder(), originalSelector) else { return } - guard let swizzledMethod = class_getInstanceMethod(self.classForCoder(), swizzledSelector) else { return } - method_exchangeImplementations(originalMethod, swizzledMethod) - } -} From 390305cf46f9221cb4357b325dd4f54a9362f8a7 Mon Sep 17 00:00:00 2001 From: James Chen Date: Mon, 3 Dec 2018 13:32:00 +0900 Subject: [PATCH 300/315] Delegate String.toBigUInt to BigUInt.init(string:) --- Neuron/Extensions/Foundation/String+Extension.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Neuron/Extensions/Foundation/String+Extension.swift b/Neuron/Extensions/Foundation/String+Extension.swift index 50cc37ee..0805474d 100644 --- a/Neuron/Extensions/Foundation/String+Extension.swift +++ b/Neuron/Extensions/Foundation/String+Extension.swift @@ -29,10 +29,6 @@ extension String { } func toBigUInt() -> BigUInt? { - if hasPrefix("0x") { - return BigUInt(removeHexPrefix(), radix: 16) - } else { - return BigUInt(self) - } + return BigUInt(string: self) } } From 0ea1de9737380d068768e008822aae9feab2ecef Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 16:27:48 +0800 Subject: [PATCH 301/315] Update dapp page title --- Neuron/en.lproj/Localizable.strings | 2 +- Neuron/zh-Hans.lproj/Localizable.strings | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Neuron/en.lproj/Localizable.strings b/Neuron/en.lproj/Localizable.strings index daa3120c..bfa97396 100644 --- a/Neuron/en.lproj/Localizable.strings +++ b/Neuron/en.lproj/Localizable.strings @@ -13,7 +13,7 @@ // DApp // --------------------------------------------------------- -"DApp.Home" = "DApps"; +"DApp.Home" = "ÐApp"; "DApp.NFT.EmptyData" = "Empty Data"; // Settings diff --git a/Neuron/zh-Hans.lproj/Localizable.strings b/Neuron/zh-Hans.lproj/Localizable.strings index 1226f779..25dafa2b 100644 --- a/Neuron/zh-Hans.lproj/Localizable.strings +++ b/Neuron/zh-Hans.lproj/Localizable.strings @@ -13,7 +13,7 @@ // DApp // --------------------------------------------------------- -"DApp.Home" = "应用"; +"DApp.Home" = "ÐApp"; "DApp.NFT.EmptyData" = "您还没有藏品数据"; // Settings From fd71000ab8270814d3141cf6227f47a0c337eb10 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 16:28:16 +0800 Subject: [PATCH 302/315] Modify token profile --- .../Transaction/TransactionHistoryViewController.swift | 2 +- Neuron/Services/Ethereum/TokenProfile.swift | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift index f9870859..67940ad0 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift @@ -127,7 +127,7 @@ class TransactionHistoryViewController: UIViewController, UITableViewDelegate, U } else if let image = profile.image { self.tokenIconView.image = image } - self.tokenAmountLabel.text = profile.possess + self.tokenAmountLabel.text = profile.priceText } private func loadMoreData() { diff --git a/Neuron/Services/Ethereum/TokenProfile.swift b/Neuron/Services/Ethereum/TokenProfile.swift index 6994c6b3..ef67eba1 100644 --- a/Neuron/Services/Ethereum/TokenProfile.swift +++ b/Neuron/Services/Ethereum/TokenProfile.swift @@ -11,6 +11,7 @@ import Alamofire import Web3swift import EthereumAddress +// TODO: Refactor struct TokenProfile: Decodable { let symbol: String let address: String @@ -20,6 +21,7 @@ struct TokenProfile: Decodable { var possess: String? var detailUrl: URL? var price: Double? + var priceText: String? struct Overview: Decodable { var zh: String @@ -48,7 +50,7 @@ extension TokenModel { private func getEthereumProfile(complection: @escaping (TokenProfile?) -> Void) { let overview = TokenProfile.Overview(zh: "Ethereum是一个运行智能合约的去中心化平台,应用将完全按照程序运作,不存在任何欺诈,审查与第三方干预的可能。") let detailUrl = URL(string: "https://ntp.staging.cryptape.com?coin=ethereum") - var profile = TokenProfile(symbol: self.symbol, address: address, overview: overview, imageUrl: nil, image: UIImage(named: "eth_logo"), possess: nil, detailUrl: detailUrl, price: nil) + var profile = TokenProfile(symbol: self.symbol, address: address, overview: overview, imageUrl: nil, image: UIImage(named: "eth_logo"), possess: nil, detailUrl: detailUrl, price: nil, priceText: nil) let currencyType = LocalCurrencyService.shared.getLocalCurrencySelect().short let symbol = self.symbol @@ -57,9 +59,10 @@ extension TokenModel { DispatchQueue.main.async { if let price = price { let amount = self.tokenBalance * price - let possess = String(format: "%@ %.2f", LocalCurrencyService.shared.getLocalCurrencySelect().symbol, amount) + let possess = String(format: "%@ %.4f", LocalCurrencyService.shared.getLocalCurrencySelect().symbol, amount) profile.possess = possess profile.price = price + profile.priceText = String(format: "%@ %.4f", LocalCurrencyService.shared.getLocalCurrencySelect().symbol, price) } complection(profile) } @@ -96,9 +99,10 @@ extension TokenModel { if var profile = profile, let price = price { let balance = self.tokenBalance let amount = balance * price - let possess = String(format: "%@ %.2f", currency.symbol, amount) + let possess = String(format: "%@ %.4f", currency.symbol, amount) profile.possess = possess profile.price = price + profile.priceText = String(format: "%@ %.4f", LocalCurrencyService.shared.getLocalCurrencySelect().symbol, price) complection(profile) } else { complection(profile) From 87f3a5c72629d84047b4dd70a69a81d6094011ca Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 16:28:32 +0800 Subject: [PATCH 303/315] Update tx history title --- .../Sections/Transaction/TransactionHistoryViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift index 67940ad0..fd0afd70 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift @@ -27,7 +27,7 @@ class TransactionHistoryViewController: UIViewController, UITableViewDelegate, U override func viewDidLoad() { super.viewDidLoad() - title = "交易列表" + title = token.symbol tableView.delegate = self tableView.dataSource = self From f9fa8032a570693d53bb7631ac75869b36fd0627 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 16:28:50 +0800 Subject: [PATCH 304/315] Contract created --- Neuron/Sections/Transaction/TradeDetailsController.swift | 4 ++-- .../Transaction/TransactionHistoryViewController.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Neuron/Sections/Transaction/TradeDetailsController.swift b/Neuron/Sections/Transaction/TradeDetailsController.swift index edb16b1d..169e28b1 100644 --- a/Neuron/Sections/Transaction/TradeDetailsController.swift +++ b/Neuron/Sections/Transaction/TradeDetailsController.swift @@ -23,7 +23,7 @@ class TradeDetailsController: UIViewController, UITableViewDataSource, UITableVi titleArr = ["区块链网络", "接受方", "发送方", "手续费", "GasPrice", "交易流水号", "所在区块", "入块时间"] subBtnArr = [ "Ethereum Mainnet", - ethereum.to, + ethereum.to.count > 0 ? ethereum.to : "Contract Created", ethereum.from, gasUsed + "eth", gasPrice + "Gwei", @@ -37,7 +37,7 @@ class TradeDetailsController: UIViewController, UITableViewDataSource, UITableVi titleArr = ["区块链网络", "接受方", "发送方", "手续费", "交易流水号", "所在区块", "入块时间"] subBtnArr = [ appChain.chainName, - appChain.to, + appChain.to.count > 0 ? appChain.to : "Contract Created", appChain.from, gasUsed + "NATT", appChain.hash, diff --git a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift index fd0afd70..3d2324b3 100644 --- a/Neuron/Sections/Transaction/TransactionHistoryViewController.swift +++ b/Neuron/Sections/Transaction/TransactionHistoryViewController.swift @@ -217,7 +217,7 @@ class TransactionHistoryTableViewCell: UITableViewCell { let amount = Double.fromAmount(transaction.value, decimals: transaction.token.decimals).decimal if transaction.from.lowercased() == walletAddress.lowercased() || transaction.from == transaction.to { - addressLabel.text = transaction.to + addressLabel.text = transaction.to.count > 0 ? transaction.to : "Contract Created" numberLabel.text = "-\(amount)" } else { addressLabel.text = transaction.from From 66ec492f2396e66c4a2fd36a8e1873c8c7acc2ba Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 16:31:11 +0800 Subject: [PATCH 305/315] Fix bug double to string --- Neuron/Extensions/Foundation/Double+Extension.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neuron/Extensions/Foundation/Double+Extension.swift b/Neuron/Extensions/Foundation/Double+Extension.swift index 374123e2..1f037746 100644 --- a/Neuron/Extensions/Foundation/Double+Extension.swift +++ b/Neuron/Extensions/Foundation/Double+Extension.swift @@ -29,7 +29,7 @@ extension Double { } func toAmount(_ decimals: Int = 18) -> BigUInt { - return Web3Utils.parseToBigUInt(description, decimals: decimals) ?? 0 + return Web3Utils.parseToBigUInt(self.decimal, decimals: decimals) ?? 0 } func gweiToWei() -> BigUInt { From a26ff535b044c42ae0457f1c73aa12559e3528e3 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 16:33:02 +0800 Subject: [PATCH 306/315] Fix dapp call native function --- Neuron.xcodeproj/project.pbxproj | 4 ++ .../DAppDeviceMotionMessageHandler.swift | 5 +- .../Handler/DAppGyroscopeMessageHandler.swift | 4 +- .../Handler/DAppNativeMessageHandler.swift | 6 +- .../DAppPermissionsMessageHandler.swift | 62 +++++++++++++++++++ 5 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 Neuron/Sections/Dapp/Handler/DAppPermissionsMessageHandler.swift diff --git a/Neuron.xcodeproj/project.pbxproj b/Neuron.xcodeproj/project.pbxproj index 3a333ec6..f2655478 100644 --- a/Neuron.xcodeproj/project.pbxproj +++ b/Neuron.xcodeproj/project.pbxproj @@ -74,6 +74,7 @@ 6E8D77F021A5264F00EA7674 /* WalletQRCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8D77EF21A5264F00EA7674 /* WalletQRCodeViewController.swift */; }; 6E8D77F221A54B8100EA7674 /* TransactionSwitchTokenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8D77F121A54B8100EA7674 /* TransactionSwitchTokenViewController.swift */; }; 6E90D06921704C1D00B98363 /* SensorsAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E90D06821704C1D00B98363 /* SensorsAnalytics.swift */; }; + 6E96B3E521B4E38C000F935B /* DAppPermissionsMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E96B3E421B4E38C000F935B /* DAppPermissionsMessageHandler.swift */; }; 6E9B947B21A4080300D67357 /* DesignableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B947A21A4080300D67357 /* DesignableButton.swift */; }; 6E9B947D21A4083100D67357 /* DesignableGradientView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B947C21A4083100D67357 /* DesignableGradientView.swift */; }; 6E9B947F21A4086400D67357 /* WalletPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B947E21A4086400D67357 /* WalletPresenter.swift */; }; @@ -286,6 +287,7 @@ 6E8D77EF21A5264F00EA7674 /* WalletQRCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletQRCodeViewController.swift; sourceTree = ""; }; 6E8D77F121A54B8100EA7674 /* TransactionSwitchTokenViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionSwitchTokenViewController.swift; sourceTree = ""; }; 6E90D06821704C1D00B98363 /* SensorsAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SensorsAnalytics.swift; sourceTree = ""; }; + 6E96B3E421B4E38C000F935B /* DAppPermissionsMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DAppPermissionsMessageHandler.swift; sourceTree = ""; }; 6E9B947A21A4080300D67357 /* DesignableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignableButton.swift; sourceTree = ""; }; 6E9B947C21A4083100D67357 /* DesignableGradientView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesignableGradientView.swift; sourceTree = ""; }; 6E9B947E21A4086400D67357 /* WalletPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletPresenter.swift; sourceTree = ""; }; @@ -700,6 +702,7 @@ 6EABFB0A21991BC200305ED5 /* DAppQRCodeMessageHandler.swift */, 6EABFB0F219933EB00305ED5 /* DAppDeviceMotionMessageHandler.swift */, 6EABFB1321995AB800305ED5 /* DAppGyroscopeMessageHandler.swift */, + 6E96B3E421B4E38C000F935B /* DAppPermissionsMessageHandler.swift */, ); path = Handler; sourceTree = ""; @@ -1464,6 +1467,7 @@ 6E876845218031670032EBCE /* UInt+Extension.swift in Sources */, 6E90D06921704C1D00B98363 /* SensorsAnalytics.swift in Sources */, 6E8BFC3121AFDBB5004752CC /* EthereumBalanceLoader.swift in Sources */, + 6E96B3E521B4E38C000F935B /* DAppPermissionsMessageHandler.swift in Sources */, 89D84CE92147D926006B0287 /* NFTDetailViewController.swift in Sources */, 898A1A0320B5717A00ECB465 /* ManageAssetViewController.swift in Sources */, 6E3B2B73219EDB870095257D /* AppChain+TransactionStatus.swift in Sources */, diff --git a/Neuron/Sections/Dapp/Handler/DAppDeviceMotionMessageHandler.swift b/Neuron/Sections/Dapp/Handler/DAppDeviceMotionMessageHandler.swift index 8d7e45ee..58537922 100644 --- a/Neuron/Sections/Dapp/Handler/DAppDeviceMotionMessageHandler.swift +++ b/Neuron/Sections/Dapp/Handler/DAppDeviceMotionMessageHandler.swift @@ -39,7 +39,6 @@ class DAppDeviceMotionMessageHandler: DAppNativeMessageHandler { guard let data = try? JSONSerialization.data(withJSONObject: message.body, options: .prettyPrinted) else { return } if message.name == MessageName.startDeviceMotionListening.rawValue { guard motionManager == nil else { - self.callback(result: .success([:])) return } let interval: Interval = (try? JSONDecoder().decode(Parameters.self, from: data))?.interval ?? .normal @@ -60,11 +59,9 @@ class DAppDeviceMotionMessageHandler: DAppNativeMessageHandler { self?.motionDidUpdate(motion: motion) }) motionManager = manager - callback(result: .success([:])) } else if message.name == MessageName.stopDeviceMotionListening.rawValue { motionManager?.stopDeviceMotionUpdates() motionManager = nil - callback(result: .success([:])) } } @@ -74,6 +71,6 @@ class DAppDeviceMotionMessageHandler: DAppNativeMessageHandler { "gamma": -motion.attitude.pitch / Double.pi * 180, // -90 ~ 90 "beta": motion.attitude.yaw / Double.pi * 180 // -180 ~ 180 ] - callback(funcName: "onDeviceMotionChange", result: ["res": result]) + callback(result: .success(["res": result])) } } diff --git a/Neuron/Sections/Dapp/Handler/DAppGyroscopeMessageHandler.swift b/Neuron/Sections/Dapp/Handler/DAppGyroscopeMessageHandler.swift index 58b9766e..6be6102f 100644 --- a/Neuron/Sections/Dapp/Handler/DAppGyroscopeMessageHandler.swift +++ b/Neuron/Sections/Dapp/Handler/DAppGyroscopeMessageHandler.swift @@ -39,7 +39,6 @@ class DAppGyroscopeMessageHandler: DAppNativeMessageHandler { guard let data = try? JSONSerialization.data(withJSONObject: message.body, options: .prettyPrinted) else { return } if message.name == MessageName.startGyroscope.rawValue { guard motionManager == nil else { - self.callback(result: .success([:])) return } let interval: Interval = (try? JSONDecoder().decode(Parameters.self, from: data))?.interval ?? .normal @@ -60,7 +59,6 @@ class DAppGyroscopeMessageHandler: DAppNativeMessageHandler { self?.motionDidUpdate(motion: motion) }) motionManager = manager - callback(result: .success([:])) } else if message.name == MessageName.stopGyroscope.rawValue { motionManager?.stopDeviceMotionUpdates() motionManager = nil @@ -74,6 +72,6 @@ class DAppGyroscopeMessageHandler: DAppNativeMessageHandler { "y": motion.rotationRate.y, "z": motion.rotationRate.z ] - callback(funcName: "onGyroscopeChange", result: ["res": result]) + callback(result: .success(["res": result])) } } diff --git a/Neuron/Sections/Dapp/Handler/DAppNativeMessageHandler.swift b/Neuron/Sections/Dapp/Handler/DAppNativeMessageHandler.swift index c00ff52b..f8d836f1 100644 --- a/Neuron/Sections/Dapp/Handler/DAppNativeMessageHandler.swift +++ b/Neuron/Sections/Dapp/Handler/DAppNativeMessageHandler.swift @@ -50,8 +50,9 @@ class DAppNativeMessageHandler: NSObject, WKScriptMessageHandler { func callback(funcName: String, result: [String: Any]) { guard let data = try? JSONSerialization.data(withJSONObject: result, options: .prettyPrinted) else { return } - let string = String(bytes: data.bytes, encoding: .utf8) ?? "" - let js = "\(funcName)(\'\(string)\')" + var string = String(bytes: data.bytes, encoding: .utf8) ?? "" + string = string.replacingOccurrences(of: "\n", with: "") + let js = "\(funcName)('\(string)')" webView?.evaluateJavaScript(js, completionHandler: nil) } } @@ -67,6 +68,7 @@ extension WKWebView { addNativeFunctionHandler(handler: DAppQRCodeMessageHandler()) addNativeFunctionHandler(handler: DAppDeviceMotionMessageHandler()) addNativeFunctionHandler(handler: DAppGyroscopeMessageHandler()) + addNativeFunctionHandler(handler: DAppPermissionsMessageHandler()) } } diff --git a/Neuron/Sections/Dapp/Handler/DAppPermissionsMessageHandler.swift b/Neuron/Sections/Dapp/Handler/DAppPermissionsMessageHandler.swift new file mode 100644 index 00000000..d493b0d4 --- /dev/null +++ b/Neuron/Sections/Dapp/Handler/DAppPermissionsMessageHandler.swift @@ -0,0 +1,62 @@ +// +// DAppCheckPermissionsMessageHandler.swift +// Neuron +// +// Created by 晨风 on 2018/12/3. +// Copyright © 2018 Cryptape. All rights reserved. +// + +import UIKit +import WebKit +import AVFoundation + +class DAppPermissionsMessageHandler: DAppNativeMessageHandler { + enum MessageName: String { + case checkPermissions + case requestPermissions + } + + struct Parameters: Decodable { + let permission: Permission + } + + enum Permission: String, Decodable { + case camera = "permission" + } + + override var messageNames: [String] { + return [ + MessageName.checkPermissions.rawValue, + MessageName.requestPermissions.rawValue + ] + } + + override func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + super.userContentController(userContentController, didReceive: message) + guard let data = try? JSONSerialization.data(withJSONObject: message.body, options: .prettyPrinted) else { return } + if message.name == MessageName.checkPermissions.rawValue { + let permission = (try? JSONDecoder().decode(Parameters.self, from: data))?.permission ?? .camera + if permission == .camera { + if AVCaptureDevice.authorizationStatus(for: .video) == .authorized { + callback(result: .success(["result": true])) + } else { + callback(result: .success(["result": false])) + } + } + } else if message.name == MessageName.requestPermissions.rawValue { + let permission = (try? JSONDecoder().decode(Parameters.self, from: data))?.permission ?? .camera + if permission == .camera { + AVCaptureDevice.requestAccess(for: .video) { (result) in + DispatchQueue.main.async { + if result { + self.callback(result: .success(["result": true])) + } else { + self.callback(result: .success(["result": false])) + } + } + } + } + } + } + +} From 7ce92c453e675a39d313660a2552a973c73ae1a2 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 16:33:26 +0800 Subject: [PATCH 307/315] Fix wallet home --- Neuron/Models/Token.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neuron/Models/Token.swift b/Neuron/Models/Token.swift index f741af21..9c56c36f 100644 --- a/Neuron/Models/Token.swift +++ b/Neuron/Models/Token.swift @@ -78,7 +78,7 @@ class Token { } let balance: BigUInt - + self.balance = nil switch type { case .appChain, .appChainErc20: balance = try AppChainNetwork.appChain(url: URL(string: chainHosts)).rpc.getBalance(address: walletAddress) From 8c9b13ab22dbf60929c960445158b670588be073 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 16:36:59 +0800 Subject: [PATCH 308/315] Fix erc20 tx fee --- .../Transaction/SendTransactionViewController.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index ced6c574..03af0c85 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -145,7 +145,7 @@ class SendTransactionViewController: UITableViewController, TransactonSender { if token.type == .erc20 { let realm = try! Realm() let ether = realm.objects(TokenModel.self).first(where: { $0.type == .ether })! - if ether.tokenBalance >= paramBuilder.txFeeNatural { + if ether.tokenBalance < paramBuilder.txFeeNatural { Toast.showToast(text: "请确保账户剩余\(token.gasSymbol)高于矿工费用,以便顺利完成转账~") return } @@ -168,7 +168,9 @@ class SendTransactionViewController: UITableViewController, TransactonSender { // TODO: FIXME: erc20 token requires ETH balance for tx fee switch token.type { case .ether, .appChain: - let amount = token.tokenBalance - paramBuilder.txFeeNatural + let balance = NSDecimalNumber(string: String(token.tokenBalance)) + let txFee = NSDecimalNumber(string: String(paramBuilder.txFeeNatural)) + let amount = balance.subtracting(txFee).doubleValue if amount < 0 { Toast.showToast(text: "请确保账户剩余\(token.gasSymbol)高于矿工费用,以便顺利完成转账~") return From 2e21163653ab99e70b597dc248a70f35df84becc Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 16:37:28 +0800 Subject: [PATCH 309/315] Update tx history UI --- Neuron/Sections/Transaction/TransactionHistory.storyboard | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Neuron/Sections/Transaction/TransactionHistory.storyboard b/Neuron/Sections/Transaction/TransactionHistory.storyboard index 653d690f..31cb3b01 100644 --- a/Neuron/Sections/Transaction/TransactionHistory.storyboard +++ b/Neuron/Sections/Transaction/TransactionHistory.storyboard @@ -107,11 +107,11 @@ - - + diff --git a/Neuron/Sections/Dapp/WebView/DappViewController.swift b/Neuron/Sections/Dapp/WebView/DappViewController.swift index 06177518..5f322286 100644 --- a/Neuron/Sections/Dapp/WebView/DappViewController.swift +++ b/Neuron/Sections/Dapp/WebView/DappViewController.swift @@ -19,6 +19,7 @@ class DappViewController: UIViewController, WKUIDelegate, ErrorOverlayPresentabl super.viewDidLoad() self.title = "DApp.Home".localized() + self.navigationItem.title = "ÐApp" addWebView() layoutWebView() diff --git a/Neuron/en.lproj/Localizable.strings b/Neuron/en.lproj/Localizable.strings index bfa97396..2a783714 100644 --- a/Neuron/en.lproj/Localizable.strings +++ b/Neuron/en.lproj/Localizable.strings @@ -13,7 +13,7 @@ // DApp // --------------------------------------------------------- -"DApp.Home" = "ÐApp"; +"DApp.Home" = "DApp"; "DApp.NFT.EmptyData" = "Empty Data"; // Settings diff --git a/Neuron/zh-Hans.lproj/Localizable.strings b/Neuron/zh-Hans.lproj/Localizable.strings index 25dafa2b..1226f779 100644 --- a/Neuron/zh-Hans.lproj/Localizable.strings +++ b/Neuron/zh-Hans.lproj/Localizable.strings @@ -13,7 +13,7 @@ // DApp // --------------------------------------------------------- -"DApp.Home" = "ÐApp"; +"DApp.Home" = "应用"; "DApp.NFT.EmptyData" = "您还没有藏品数据"; // Settings From 3b6d602b2e3feb2e2d3215946a9d1fcece369ac8 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 18:48:12 +0800 Subject: [PATCH 313/315] Fix send tx value --- Neuron/Extensions/Foundation/Double+Extension.swift | 3 ++- .../Sections/Common/PageItems/TxSummaryPageItem.swift | 2 +- Neuron/Sections/Dapp/WebView/ContractController.swift | 1 + .../Transaction/SendTransactionViewController.swift | 11 +++++------ .../Transaction/TransactionParamBuilder.swift | 7 ++++++- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Neuron/Extensions/Foundation/Double+Extension.swift b/Neuron/Extensions/Foundation/Double+Extension.swift index 1f037746..94400a0e 100644 --- a/Neuron/Extensions/Foundation/Double+Extension.swift +++ b/Neuron/Extensions/Foundation/Double+Extension.swift @@ -29,7 +29,8 @@ extension Double { } func toAmount(_ decimals: Int = 18) -> BigUInt { - return Web3Utils.parseToBigUInt(self.decimal, decimals: decimals) ?? 0 + let stringValue = NSDecimalNumber(value: self).stringValue + return Web3Utils.parseToBigUInt(stringValue, decimals: decimals) ?? 0 } func gweiToWei() -> BigUInt { diff --git a/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift b/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift index eb695884..5e7b0691 100644 --- a/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift +++ b/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift @@ -24,7 +24,7 @@ class TxSummaryPageItem: BLTNPageItem { if param.amount >= 0.00000001 { summaryView.amountLabel.text = Double.fromAmount(param.value, decimals: param.decimals).decimal + " " } else { - summaryView.amountLabel.text = String(param.amount) + " " + summaryView.amountLabel.text = NSDecimalNumber(value: param.amount).stringValue + " " } let range = NSRange(location: summaryView.amountLabel.text!.lengthOfBytes(using: .utf8), length: param.symbol.lengthOfBytes(using: .utf8)) diff --git a/Neuron/Sections/Dapp/WebView/ContractController.swift b/Neuron/Sections/Dapp/WebView/ContractController.swift index aa7e38ab..b62e9c14 100644 --- a/Neuron/Sections/Dapp/WebView/ContractController.swift +++ b/Neuron/Sections/Dapp/WebView/ContractController.swift @@ -231,6 +231,7 @@ class ContractController: UITableViewController, TransactonSender { } paramBuilder.from = AppModel.current.currentWallet!.address paramBuilder.value = Double(value)!.toAmount(tokenModel.decimals) + paramBuilder.amount = NSDecimalNumber(string: value).doubleValue switch chainType { case .appChain: diff --git a/Neuron/Sections/Transaction/SendTransactionViewController.swift b/Neuron/Sections/Transaction/SendTransactionViewController.swift index 03af0c85..4a401a01 100644 --- a/Neuron/Sections/Transaction/SendTransactionViewController.swift +++ b/Neuron/Sections/Transaction/SendTransactionViewController.swift @@ -138,7 +138,7 @@ class SendTransactionViewController: UITableViewController, TransactonSender { view.endEditing(true) let amount = Double(amountTextField.text!) ?? 0.0 - paramBuilder.value = amount.toAmount(token.decimals) + paramBuilder.value = amount.toAmount() paramBuilder.to = addressTextField.text! paramBuilder.amount = amount @@ -170,13 +170,13 @@ class SendTransactionViewController: UITableViewController, TransactonSender { case .ether, .appChain: let balance = NSDecimalNumber(string: String(token.tokenBalance)) let txFee = NSDecimalNumber(string: String(paramBuilder.txFeeNatural)) - let amount = balance.subtracting(txFee).doubleValue - if amount < 0 { + let amount = balance.subtracting(txFee) + if amount.doubleValue < 0 { Toast.showToast(text: "请确保账户剩余\(token.gasSymbol)高于矿工费用,以便顺利完成转账~") return } - amountTextField.text = "\(amount)" - paramBuilder.value = amount.toAmount(token.decimals) + amountTextField.text = amount.stringValue + paramBuilder.value = amount.doubleValue.toAmount(token.decimals) case .erc20: let realm = try! Realm() let ether = realm.objects(TokenModel.self).first(where: { $0.type == .ether })! @@ -284,7 +284,6 @@ private extension SendTransactionViewController { self.transactionAvailableBalance() })) alert.addAction(UIAlertAction(title: "取消", style: .destructive, handler: { (_) in - self.amountTextField.text = "" })) present(alert, animated: true, completion: nil) return false diff --git a/Neuron/Sections/Transaction/TransactionParamBuilder.swift b/Neuron/Sections/Transaction/TransactionParamBuilder.swift index 927f5093..1452a4a5 100644 --- a/Neuron/Sections/Transaction/TransactionParamBuilder.swift +++ b/Neuron/Sections/Transaction/TransactionParamBuilder.swift @@ -42,6 +42,7 @@ class TransactionParamBuilder: NSObject { private(set) var txFeeNatural: Double = 0 var tokenBalance: BigUInt = 0 + var balance: Double = 0 @objc dynamic private(set) var tokenPrice: Double = 0 @@ -53,7 +54,9 @@ class TransactionParamBuilder: NSObject { var hasSufficientBalance: Bool { switch tokenType { case .ether, .appChain: - return tokenBalance >= txFee + value + let amount = NSDecimalNumber(string: self.amount.description).adding(NSDecimalNumber(string: txFeeNatural.description)) + let balance = NSDecimalNumber(string: self.balance.description) + return balance.doubleValue >= amount.doubleValue case .erc20, .appChainErc20: return tokenBalance >= value } @@ -76,6 +79,7 @@ class TransactionParamBuilder: NSObject { symbol = token.symbol nativeCoinSymbol = token.gasSymbol tokenBalance = token.tokenBalance.toAmount(token.decimals) + balance = token.tokenBalance super.init() @@ -93,6 +97,7 @@ class TransactionParamBuilder: NSObject { symbol = builder.symbol nativeCoinSymbol = builder.nativeCoinSymbol tokenBalance = builder.tokenBalance + balance = builder.balance super.init() gasPrice = builder.gasPrice gasLimit = builder.gasLimit From 92af9e9e37f787f33a3d367fd559e450cd635d51 Mon Sep 17 00:00:00 2001 From: cezres Date: Mon, 3 Dec 2018 18:58:42 +0800 Subject: [PATCH 314/315] Fix tx amount --- Neuron/Extensions/Foundation/Double+Extension.swift | 2 +- Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Neuron/Extensions/Foundation/Double+Extension.swift b/Neuron/Extensions/Foundation/Double+Extension.swift index 94400a0e..59874b4b 100644 --- a/Neuron/Extensions/Foundation/Double+Extension.swift +++ b/Neuron/Extensions/Foundation/Double+Extension.swift @@ -29,7 +29,7 @@ extension Double { } func toAmount(_ decimals: Int = 18) -> BigUInt { - let stringValue = NSDecimalNumber(value: self).stringValue + let stringValue = NSDecimalNumber(string: self.description).stringValue return Web3Utils.parseToBigUInt(stringValue, decimals: decimals) ?? 0 } diff --git a/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift b/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift index 5e7b0691..7161cba9 100644 --- a/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift +++ b/Neuron/Sections/Common/PageItems/TxSummaryPageItem.swift @@ -24,7 +24,7 @@ class TxSummaryPageItem: BLTNPageItem { if param.amount >= 0.00000001 { summaryView.amountLabel.text = Double.fromAmount(param.value, decimals: param.decimals).decimal + " " } else { - summaryView.amountLabel.text = NSDecimalNumber(value: param.amount).stringValue + " " + summaryView.amountLabel.text = param.amount.description + " " } let range = NSRange(location: summaryView.amountLabel.text!.lengthOfBytes(using: .utf8), length: param.symbol.lengthOfBytes(using: .utf8)) From c00fb1369017b44037e0a7654b6da9f3b0aef41d Mon Sep 17 00:00:00 2001 From: LuFP Date: Mon, 3 Dec 2018 19:19:08 +0800 Subject: [PATCH 315/315] Modify DApp url. --- Neuron/Sections/Dapp/WebView/DappViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neuron/Sections/Dapp/WebView/DappViewController.swift b/Neuron/Sections/Dapp/WebView/DappViewController.swift index 5f322286..6eaf556b 100644 --- a/Neuron/Sections/Dapp/WebView/DappViewController.swift +++ b/Neuron/Sections/Dapp/WebView/DappViewController.swift @@ -13,7 +13,7 @@ import JavaScriptCore /// DApp Home class DappViewController: UIViewController, WKUIDelegate, ErrorOverlayPresentable { private let webView = WKWebView(frame: .zero) - private var mainUrl = URL(string: "https://dapp.staging.cryptape.com")! + private var mainUrl = URL(string: "https://dapp.cryptape.com")! override func viewDidLoad() { super.viewDidLoad()