After scanning QR code during import wallet during onboarding, switch to the appropriate tab #2354

pull/2376/head
Vladyslav shepitko 4 years ago committed by James Sangalli
parent 856fa4b2cc
commit ff8de1c65d
  1. 107
      AlphaWallet/Browser/Coordinators/QRCodeResolutionCoordinator.swift
  2. 9
      AlphaWallet/Extensions/String.swift
  3. 12
      AlphaWallet/Tokens/Coordinators/TokensCoordinator.swift
  4. 6
      AlphaWallet/Tokens/Views/SegmentedControl.swift
  5. 86
      AlphaWallet/Wallet/Coordinators/WalletCoordinator.swift
  6. 12
      AlphaWallet/Wallet/ViewControllers/ImportWalletViewController.swift

@ -1,5 +1,5 @@
//
// File.swift
// QRCodeResolutionCoordinator.swift
// AlphaWallet
//
// Created by Vladyslav Shepitko on 10.09.2020.
@ -15,6 +15,9 @@ protocol QRCodeResolutionCoordinatorDelegate: class {
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveWalletConnectURL url: WalletConnectURL)
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveString value: String)
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveURL url: URL)
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveJSON json: String)
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveSeedPhase seedPhase: [String])
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolvePrivateKey privateKey: String)
func didCancel(in coordinator: QRCodeResolutionCoordinator)
}
@ -44,16 +47,32 @@ private enum ScanQRCodeResolution {
case walletConnect(WalletConnectURL)
case other(String)
case url(URL)
case privateKey(String)
case seedPhase([String])
case json(String)
init(rawValue: String) {
if let value = QRCodeValueParser.from(string: rawValue.trimmed) {
let trimmedValue = rawValue.trimmed
if let value = QRCodeValueParser.from(string: trimmedValue) {
self = .value(value: value)
} else if let rawValue = WalletConnectURL(rawValue) {
self = .walletConnect(rawValue)
} else if let url = URL(string: rawValue) {
} else if let url = WalletConnectURL(rawValue) {
self = .walletConnect(url)
} else if let url = URL(string: trimmedValue), trimmedValue.isValidURL {
self = .url(url)
} else {
self = .other(rawValue)
if trimmedValue.isValidJSON {
self = .json(trimmedValue)
} else if trimmedValue.isPrivateKey {
self = .privateKey(trimmedValue)
} else {
let components = trimmedValue.components(separatedBy: " ")
if components.isEmpty || components.count == 1 {
self = .other(trimmedValue)
} else {
self = .seedPhase(components)
}
}
}
}
}
@ -67,8 +86,8 @@ private enum CheckEIP681Error: Error {
final class QRCodeResolutionCoordinator: Coordinator {
private let tokensDatastores: [TokensDataStore]
private let assetDefinitionStore: AssetDefinitionStore
private let tokensDatastores: [TokensDataStore]?
private let assetDefinitionStore: AssetDefinitionStore?
private var skipResolvedCodes: Bool = false
private var navigationController: UINavigationController {
scanQRCodeCoordinator.parentNavigationController
@ -80,7 +99,7 @@ final class QRCodeResolutionCoordinator: Coordinator {
var coordinators: [Coordinator] = []
weak var delegate: QRCodeResolutionCoordinatorDelegate?
init(coordinator: ScanQRCodeCoordinator, tokensDatastores: [TokensDataStore], assetDefinitionStore: AssetDefinitionStore) {
init(coordinator: ScanQRCodeCoordinator, tokensDatastores: [TokensDataStore]?, assetDefinitionStore: AssetDefinitionStore?) {
self.tokensDatastores = tokensDatastores
self.scanQRCodeCoordinator = coordinator
self.assetDefinitionStore = assetDefinitionStore
@ -107,35 +126,42 @@ extension QRCodeResolutionCoordinator: ScanQRCodeCoordinatorDelegate {
resolveScanResult(result)
}
private func availableActions(forContract contract: AlphaWallet.Address) -> [ScanQRCodeAction] {
//NOTE: or maybe we need pass though all servers?
guard let tokensDatastore = tokensDatastores?.first(where: { $0.server == rpcServer }) else {
return []
}
//I guess if we have token, we shouldn't be able to send to it, or we should?
if tokensDatastore.token(forContract: contract) != nil {
return [.sendToAddress, .watchWallet, .openInEtherscan]
} else {
return [.sendToAddress, .addCustomToken, .watchWallet, .openInEtherscan]
}
}
private func resolveScanResult(_ rawValue: String) {
guard let delegate = delegate else { return }
let rpcServer = self.rpcServer
switch ScanQRCodeResolution(rawValue: rawValue) {
case .value(let value):
switch value {
case .address(let contract):
let actions: [ScanQRCodeAction]
//NOTE: or maybe we need pass though all servers?
guard let tokensDatastore = tokensDatastores.first(where: { $0.server == rpcServer }) else { return }
//I guess if we have token, we shouldn't be able to send to it, or we should?
if tokensDatastore.token(forContract: contract) != nil {
actions = [.sendToAddress, .watchWallet, .openInEtherscan]
let actions = availableActions(forContract: contract)
if actions.isEmpty {
delegate.coordinator(self, didResolveAddress: contract, action: .watchWallet)
} else {
actions = [.sendToAddress, .addCustomToken, .watchWallet, .openInEtherscan]
showDidScanWalletAddress(for: actions, completion: { action in
delegate.coordinator(self, didResolveAddress: contract, action: action)
}, cancelCompletion: {
self.skipResolvedCodes = false
})
}
showDidScanWalletAddress(for: actions, completion: { action in
delegate.coordinator(self, didResolveAddress: contract, action: action)
}, cancelCompletion: {
self.skipResolvedCodes = false
})
case .eip681(let protocolName, let address, let function, let params):
guard let tokensDatastores = tokensDatastores, let assetDefinitionStore = assetDefinitionStore else { return }
let data = CheckEIP681Params(protocolName: protocolName, address: address, functionName: function, params: params, rpcServer: rpcServer)
self.checkEIP681(data).done { result in
self.checkEIP681(data, tokensDatastores: tokensDatastores, assetDefinitionStore: assetDefinitionStore).done { result in
delegate.coordinator(self, didResolveTransactionType: result.transactionType, token: result.token)
}.cauterize()
}
@ -150,6 +176,12 @@ extension QRCodeResolutionCoordinator: ScanQRCodeCoordinatorDelegate {
//NOTE: we need to reset flat to false to make sure that next detected QR code will be handled
self.skipResolvedCodes = false
})
case .json(let value):
delegate.coordinator(self, didResolveJSON: value)
case .privateKey(let value):
delegate.coordinator(self, didResolvePrivateKey: value)
case .seedPhase(let value):
delegate.coordinator(self, didResolveSeedPhase: value)
}
}
@ -204,13 +236,13 @@ extension QRCodeResolutionCoordinator: ScanQRCodeCoordinatorDelegate {
let rpcServer: RPCServer
}
private func checkEIP681(_ params: CheckEIP681Params) -> Promise<(transactionType: TransactionType, token: TokenObject)> {
private func checkEIP681(_ params: CheckEIP681Params, tokensDatastores: [TokensDataStore], assetDefinitionStore: AssetDefinitionStore) -> Promise<(transactionType: TransactionType, token: TokenObject)> {
return Eip681Parser(protocolName: params.protocolName, address: params.address, functionName: params.functionName, params: params.params).parse().then { result -> Promise<(transactionType: TransactionType, token: TokenObject)> in
guard let (contract: contract, customServer, recipient, maybeScientificAmountString) = result.parameters else {
return .init(error: CheckEIP681Error.parameterInvalid)
}
guard let storage = self.tokensDatastores.first(where: { $0.server == customServer ?? params.rpcServer }) else {
guard let storage = tokensDatastores.first(where: { $0.server == customServer ?? params.rpcServer }) else {
return .init(error: CheckEIP681Error.missingRpcServer)
}
@ -224,7 +256,7 @@ extension QRCodeResolutionCoordinator: ScanQRCodeCoordinatorDelegate {
return .value((transactionType, token))
} else {
return Promise { resolver in
fetchContractDataFor(address: contract, storage: storage, assetDefinitionStore: self.assetDefinitionStore) { result in
fetchContractDataFor(address: contract, storage: storage, assetDefinitionStore: assetDefinitionStore) { result in
switch result {
case .name, .symbol, .balance, .decimals, .nonFungibleTokenComplete, .delegateTokenComplete, .failed:
resolver.reject(CheckEIP681Error.contractInvalid)
@ -253,6 +285,7 @@ extension QRCodeResolutionCoordinator: ScanQRCodeCoordinatorDelegate {
}
private extension String {
var scientificAmountToBigInt: BigInt? {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
@ -261,4 +294,20 @@ private extension String {
let amountString = numberFormatter.number(from: self).flatMap { numberFormatter.string(from: $0) }
return amountString.flatMap { BigInt($0) }
}
var isValidJSON: Bool {
guard let jsonData = self.data(using: .utf8) else { return false }
return (try? JSONSerialization.jsonObject(with: jsonData)) != nil
}
var isValidURL: Bool {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
if let match = detector.firstMatch(in: self, options: [], range: NSRange(location: 0, length: utf16.count)) {
// it is a link, if the match covers the whole string
return match.range.length == utf16.count
} else {
return false
}
}
}

@ -58,6 +58,15 @@ extension String {
return [:]
}
var has0xPrefix: Bool {
return hasPrefix("0x")
}
var isPrivateKey: Bool {
let value = self.drop0x.components(separatedBy: " ").joined()
return value.count == 64
}
var drop0x: String {
if count > 2 && substring(with: 0..<2) == "0x" {
return String(dropFirst(2))

@ -252,6 +252,18 @@ extension TokensCoordinator: SelectAssetCoordinatorDelegate {
extension TokensCoordinator: QRCodeResolutionCoordinatorDelegate {
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveJSON json: String) {
removeCoordinator(coordinator)
}
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveSeedPhase seedPhase: [String]) {
removeCoordinator(coordinator)
}
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolvePrivateKey privateKey: String) {
removeCoordinator(coordinator)
}
func didCancel(in coordinator: QRCodeResolutionCoordinator) {
removeCoordinator(coordinator)
}

@ -36,7 +36,7 @@ class SegmentedControl: UIView {
backgroundColor = viewModel.backgroundColor
for each in buttons {
each.addTarget(self, action: #selector(segmentTapped(_:)), for: .touchUpInside)
each.addTarget(self, action: #selector(segmentTapped), for: .touchUpInside)
}
let buttonsStackView = buttons.map { $0 as UIView }.asStackView(spacing: 20)
buttonsStackView.translatesAutoresizingMaskIntoConstraints = false
@ -76,7 +76,7 @@ class SegmentedControl: UIView {
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
return nil
}
private static func createButtons(fromTitles titles: [String]) -> [UIButton] {
@ -117,7 +117,7 @@ class SegmentedControl: UIView {
if let constraints = highlightBarHorizontalConstraints {
NSLayoutConstraint.activate(constraints)
}
UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 10, options: UIView.AnimationOptions.allowUserInteraction, animations: {
UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 10, options: .allowUserInteraction, animations: {
self.layoutIfNeeded()
})
case .unselected:

@ -150,25 +150,13 @@ extension WalletCoordinator: WelcomeViewControllerDelegate {
}
}
extension WalletCoordinator: ScanQRCodeCoordinatorDelegate {
func didCancel(in coordinator: ScanQRCodeCoordinator) {
removeCoordinator(coordinator)
}
func didScan(result: String, in coordinator: ScanQRCodeCoordinator) {
removeCoordinator(coordinator)
importWalletViewController?.didScanQRCode(result)
}
}
extension WalletCoordinator: ImportWalletViewControllerDelegate {
func openQRCode(in controller: ImportWalletViewController) {
guard navigationController.ensureHasDeviceAuthorization() else { return }
let coordinator = ScanQRCodeCoordinator(navigationController: navigationController, account: keystore.recentlyUsedWallet, server: config.server)
let scanQRCodeCoordinator = ScanQRCodeCoordinator(navigationController: navigationController, account: keystore.recentlyUsedWallet, server: config.server)
let coordinator = QRCodeResolutionCoordinator(coordinator: scanQRCodeCoordinator, tokensDatastores: nil, assetDefinitionStore: nil)
coordinator.delegate = self
addCoordinator(coordinator)
@ -181,7 +169,63 @@ extension WalletCoordinator: ImportWalletViewControllerDelegate {
}
}
extension WalletCoordinator: QRCodeResolutionCoordinatorDelegate {
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveAddress address: AlphaWallet.Address, action: ScanQRCodeAction) {
removeCoordinator(coordinator)
importWalletViewController?.set(tabSelection: .watch)
importWalletViewController?.setValueForCurrentField(string: address.eip55String)
}
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveTransactionType transactionType: TransactionType, token: TokenObject) {
removeCoordinator(coordinator)
//no op
}
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveWalletConnectURL url: WalletConnectURL) {
removeCoordinator(coordinator)
//no op
}
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveString value: String) {
removeCoordinator(coordinator)
//no op
}
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveURL url: URL) {
removeCoordinator(coordinator)
//no op
}
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveJSON json: String) {
removeCoordinator(coordinator)
importWalletViewController?.set(tabSelection: .keystore)
importWalletViewController?.setValueForCurrentField(string: json)
}
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolveSeedPhase seedPhase: [String]) {
removeCoordinator(coordinator)
importWalletViewController?.set(tabSelection: .mnemonic)
importWalletViewController?.setValueForCurrentField(string: seedPhase.joined(separator: " "))
}
func coordinator(_ coordinator: QRCodeResolutionCoordinator, didResolvePrivateKey privateKey: String) {
removeCoordinator(coordinator)
importWalletViewController?.set(tabSelection: .privateKey)
importWalletViewController?.setValueForCurrentField(string: privateKey)
}
func didCancel(in coordinator: QRCodeResolutionCoordinator) {
removeCoordinator(coordinator)
}
}
extension WalletCoordinator: CreateInitialWalletViewControllerDelegate {
func didTapCreateWallet(inViewController viewController: CreateInitialWalletViewController) {
createInstantWallet()
}
@ -196,14 +240,16 @@ extension WalletCoordinator: CreateInitialWalletViewControllerDelegate {
}
extension WalletCoordinator: WalletCoordinatorDelegate {
func didFinish(with account: Wallet, in coordinator: WalletCoordinator) {
coordinator.navigationController.dismiss(animated: false, completion: nil)
self.removeCoordinator(coordinator)
self.delegate?.didFinish(with: account, in: self)
coordinator.navigationController.dismiss(animated: false)
removeCoordinator(coordinator)
delegate?.didFinish(with: account, in: self)
}
func didCancel(in coordinator: WalletCoordinator) {
coordinator.navigationController.dismiss(animated: true, completion: nil)
self.removeCoordinator(coordinator)
coordinator.navigationController.dismiss(animated: true)
removeCoordinator(coordinator)
}
}

@ -69,7 +69,7 @@ class ImportWalletViewController: UIViewController {
button.frame = .init(x: 0, y: 0, width: 30, height: 30)
button.setImage(R.image.togglePassword(), for: .normal)
button.tintColor = .init(red: 111, green: 111, blue: 111)
button.addTarget(self, action: #selector(self.toggleMaskPassword), for: .touchUpInside)
button.addTarget(self, action: #selector(toggleMaskPassword), for: .touchUpInside)
return button
}()
textField.textField.rightViewMode = .unlessEditing
@ -521,9 +521,12 @@ class ImportWalletViewController: UIViewController {
delegate?.openQRCode(in: self)
}
func set(tabSelection selection: ImportWalletTab) {
tabBar.selection = .selected(selection.selectionIndex)
}
func setValueForCurrentField(string: String) {
guard let tab = viewModel.convertSegmentedControlSelectionToFilter(tabBar.selection) else { return }
switch tab {
switch viewModel.convertSegmentedControlSelectionToFilter(tabBar.selection) {
case .mnemonic:
mnemonicTextView.value = string
case .keystore:
@ -532,7 +535,10 @@ class ImportWalletViewController: UIViewController {
privateKeyTextView.value = string
case .watch:
watchAddressTextField.value = string
case .none:
break
}
showCorrectTab()
}

Loading…
Cancel
Save