Implement designed Prompts for EIP3085 wallet_addEthereumChain support #2908

pull/3215/head
Vladyslav shepitko 3 years ago
parent 0f411a6efc
commit 2094623d8f
  1. 8
      AlphaWallet.xcodeproj/project.pbxproj
  2. 100
      AlphaWallet/Browser/Coordinators/DappRequestSwitchCustomChainCoordinator.swift
  3. 180
      AlphaWallet/Browser/ViewControllers/SwitchChainRequestViewController.swift
  4. 53
      AlphaWallet/Browser/ViewModel/SwitchChainRequestViewModel.swift
  5. 4
      AlphaWallet/Localization/en.lproj/Localizable.strings
  6. 4
      AlphaWallet/Localization/es.lproj/Localizable.strings
  7. 4
      AlphaWallet/Localization/ja.lproj/Localizable.strings
  8. 4
      AlphaWallet/Localization/ko.lproj/Localizable.strings
  9. 4
      AlphaWallet/Localization/zh-Hans.lproj/Localizable.strings
  10. 2
      AlphaWallet/Settings/Coordinators/AddRPCServerCoordinator.swift

@ -841,6 +841,8 @@
879F185C26E74507000602F2 /* ButtonsBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 879F185B26E74507000602F2 /* ButtonsBar.swift */; };
879F185E26E74512000602F2 /* ButtonsBarBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 879F185D26E74512000602F2 /* ButtonsBarBackgroundView.swift */; };
879F186026E74543000602F2 /* ToolButtonsBarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 879F185F26E74543000602F2 /* ToolButtonsBarView.swift */; };
87A05C7526FCCA5C00AE26CA /* SwitchChainRequestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A05C7426FCCA5C00AE26CA /* SwitchChainRequestViewController.swift */; };
87A05C7726FCCA8B00AE26CA /* SwitchChainRequestViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A05C7626FCCA8B00AE26CA /* SwitchChainRequestViewModel.swift */; };
87A0C93225AEF1E400E73F60 /* EventInstanceValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A0C93125AEF1E400E73F60 /* EventInstanceValue.swift */; };
87A3020924BEE243000DF32E /* TransactionInProgressViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A3020824BEE243000DF32E /* TransactionInProgressViewController.swift */; };
87A3020B24BF04B6000DF32E /* TransactionInProgressViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87A3020A24BF04B6000DF32E /* TransactionInProgressViewModel.swift */; };
@ -1839,6 +1841,8 @@
879F185B26E74507000602F2 /* ButtonsBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ButtonsBar.swift; sourceTree = "<group>"; };
879F185D26E74512000602F2 /* ButtonsBarBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonsBarBackgroundView.swift; sourceTree = "<group>"; };
879F185F26E74543000602F2 /* ToolButtonsBarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolButtonsBarView.swift; sourceTree = "<group>"; };
87A05C7426FCCA5C00AE26CA /* SwitchChainRequestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchChainRequestViewController.swift; sourceTree = "<group>"; };
87A05C7626FCCA8B00AE26CA /* SwitchChainRequestViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwitchChainRequestViewModel.swift; sourceTree = "<group>"; };
87A0C93125AEF1E400E73F60 /* EventInstanceValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventInstanceValue.swift; sourceTree = "<group>"; };
87A3020824BEE243000DF32E /* TransactionInProgressViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionInProgressViewController.swift; sourceTree = "<group>"; };
87A3020A24BF04B6000DF32E /* TransactionInProgressViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionInProgressViewModel.swift; sourceTree = "<group>"; };
@ -3044,6 +3048,7 @@
76F1DAFCBB43B6639472A229 /* BrowserHistoryViewController.swift */,
5E7C76EE22984D66A3C18E70 /* DappsAutoCompletionViewController.swift */,
5E7C7F55495A6095B3E86248 /* EditMyDappViewController.swift */,
87A05C7426FCCA5C00AE26CA /* SwitchChainRequestViewController.swift */,
);
path = ViewControllers;
sourceTree = "<group>";
@ -4219,6 +4224,7 @@
5E7C776B129861728FFB8CC8 /* EditMyDappViewControllerViewModel.swift */,
5E7C7D8D618A8A8D55479CDF /* Dapp.swift */,
5E7C7C0AC267283F3F2A6E37 /* EmptyDapps.swift */,
87A05C7626FCCA8B00AE26CA /* SwitchChainRequestViewModel.swift */,
);
path = ViewModel;
sourceTree = "<group>";
@ -5541,6 +5547,7 @@
5E7C7C98EAF40E8110241DBD /* NonFungibleTokenViewCell.swift in Sources */,
5E7C71B52A77008694BFA5D1 /* TokensDataStore.swift in Sources */,
87374A4325DFAF7800267160 /* HoneySwap.swift in Sources */,
87A05C7526FCCA5C00AE26CA /* SwitchChainRequestViewController.swift in Sources */,
5E7C75C99B9F595F26EDC405 /* LockPasscodeViewController.swift in Sources */,
5E7C710331196CD591B51785 /* LockCreatePasscodeViewController.swift in Sources */,
87F4D41E26C26C3A00EFB9BC /* SortTokensParam.swift in Sources */,
@ -5578,6 +5585,7 @@
5E7C7FCC321493B41C1083C1 /* EnterSellTokensCardPriceQuantityViewControllerViewModel.swift in Sources */,
5E7C7669BBE6255A2377E070 /* SetSellTokensCardExpiryDateViewController.swift in Sources */,
5E7C7A4384A8E3F22D3F8249 /* SetSellTokensCardExpiryDateViewControllerViewModel.swift in Sources */,
87A05C7726FCCA8B00AE26CA /* SwitchChainRequestViewModel.swift in Sources */,
5E7C7B0367CFB413C6885474 /* GenerateSellMagicLinkViewControllerViewModel.swift in Sources */,
5E7C7692C981580CD32228EB /* ChooseTokenCardTransferModeViewController.swift in Sources */,
5E7C74DBAE43954C185057B3 /* ChooseTokenCardTransferModeViewControllerViewModel.swift in Sources */,

@ -66,62 +66,62 @@ class DappRequestSwitchCustomChainCoordinator: NSObject, Coordinator {
}
private func promptAndActivateExistingServer(existingServer: RPCServer, inViewController viewController: UIViewController, callbackID: Int) {
let title = R.string.localizable.addCustomChainEnableExisting(existingServer.displayName, existingServer.chainID)
UIAlertController.alert(title: title,
message: nil,
alertButtonTitles: [R.string.localizable.oK(), R.string.localizable.cancel()],
alertButtonStyles: [.destructive, .cancel],
viewController: viewController,
completion: { [self] choice in
if choice == 0 {
let enableChain = EnableChain(existingServer, restartQueue: self.restartQueue, url: self.currentUrl)
enableChain.delegate = self
enableChain.run()
} else {
self.delegate?.userCancelled(withCallbackId: callbackID, inCoordinator: self)
}
})
func runEnableChain() {
let enableChain = EnableChain(existingServer, restartQueue: restartQueue, url: currentUrl)
enableChain.delegate = self
enableChain.run()
}
let configuration: SwitchChainRequestConfiguration = .promptAndActivateExistingServer(existingServer: existingServer)
SwitchChainRequestViewController.promise(viewController, configuration: configuration).done { result in
// NOTE: here we pretty sure that there is only one action
switch result {
case .action:
runEnableChain()
case .canceled:
self.delegate?.userCancelled(withCallbackId: callbackID, inCoordinator: self)
}
}.cauterize()
}
private func promptAndAddAndActivateServer(customChain: WalletAddEthereumChainObject, customChainId: Int, inViewController viewController: UIViewController, callbackID: Int) {
let title = R.string.localizable.addCustomChainAddAndSwitch(customChain.chainName ?? R.string.localizable.addCustomChainUnnamed(), customChainId)
UIAlertController.alert(title: title,
message: nil,
alertButtonTitles: [R.string.localizable.settingsEnabledNetworksMainnet(), R.string.localizable.settingsEnabledNetworksTestnet(), R.string.localizable.cancel()],
alertButtonStyles: [.destructive, .destructive, .cancel],
viewController: viewController,
completion: { [self] choice in
func runAddCustomChain(isTestnet: Bool) {
let addCustomChain = AddCustomChain(customChain, isTestnet: isTestnet, restartQueue: self.restartQueue, url: self.currentUrl)
self.addCustomChain = (chain: addCustomChain, callbackId: callbackID)
addCustomChain.delegate = self
addCustomChain.run()
}
switch choice {
case 0:
runAddCustomChain(isTestnet: false)
case 1:
runAddCustomChain(isTestnet: true)
default:
self.delegate?.userCancelled(withCallbackId: callbackID, inCoordinator: self)
}
})
func runAddCustomChain(isTestnet: Bool) {
let addCustomChain = AddCustomChain(customChain, isTestnet: isTestnet, restartQueue: restartQueue, url: currentUrl)
self.addCustomChain = (chain: addCustomChain, callbackId: callbackID)
addCustomChain.delegate = self
addCustomChain.run()
}
let configuration: SwitchChainRequestConfiguration = .promptAndAddAndActivateServer(customChain: customChain, customChainId: customChainId)
SwitchChainRequestViewController.promise(viewController, configuration: configuration).done { result in
// NOTE: here we pretty sure that there is only one action
switch result {
case .action(let choice):
switch choice {
case 0:
runAddCustomChain(isTestnet: false)
case 1:
runAddCustomChain(isTestnet: true)
default:
self.delegate?.userCancelled(withCallbackId: callbackID, inCoordinator: self)
}
case .canceled:
self.delegate?.userCancelled(withCallbackId: callbackID, inCoordinator: self)
}
}.cauterize()
}
private func promptAndSwitchToExistingServerInBrowser(existingServer: RPCServer, viewController: UIViewController, callbackID: Int) {
let title = R.string.localizable.addCustomChainSwitchToExisting(existingServer.displayName, existingServer.chainID)
UIAlertController.alert(title: title,
message: nil,
alertButtonTitles: [R.string.localizable.oK(), R.string.localizable.cancel()],
alertButtonStyles: [.destructive, .cancel],
viewController: viewController,
completion: { [self] choice in
if choice == 0 {
self.delegate?.switchBrowserToExistingServer(existingServer, url: self.currentUrl, inCoordinator: self)
} else {
self.delegate?.userCancelled(withCallbackId: callbackID, inCoordinator: self)
}
})
let configuration: SwitchChainRequestConfiguration = .promptAndSwitchToExistingServerInBrowser(existingServer: existingServer)
SwitchChainRequestViewController.promise(viewController, configuration: configuration).done { result in
// NOTE: here we pretty sure that there is only one action
switch result {
case .action:
self.delegate?.switchBrowserToExistingServer(existingServer, url: self.currentUrl, inCoordinator: self)
case .canceled:
self.delegate?.userCancelled(withCallbackId: callbackID, inCoordinator: self)
}
}.cauterize()
}
//This is really only (and should only be) fired when the chain is already enabled and activated in browser. i.e. we are not supposed to have restarted the app UI or browser. It's a no-op. If DApps detect that the browser is already connected to the right chain, they might not even trigger this

@ -0,0 +1,180 @@
//
// SwitchChainRequestViewController.swift
// AlphaWallet
//
// Created by Vladyslav Shepitko on 23.09.2021.
//
import UIKit
import PromiseKit
protocol SwitchChainRequestViewControllerDelegate: class {
func didClose(in viewController: SwitchChainRequestViewController)
func didSelectActionButton(in viewController: SwitchChainRequestViewController)
func didSelectAdditionalButton(in viewController: SwitchChainRequestViewController)
}
class SwitchChainRequestViewController: ModalViewController {
weak var _delegate: SwitchChainRequestViewControllerDelegate?
private var titleLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.textAlignment = .center
v.textColor = R.color.black()
v.font = Fonts.bold(size: 24)
return v
}()
private var descriptionLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.textAlignment = .center
v.textColor = R.color.mine()
v.font = Fonts.regular(size: 17)
return v
}()
private lazy var enableTestnetButton: Button = {
let button = Button(size: .normal, style: .system)
button.setTitle(viewModel.additionalButtonTitle, for: .normal)
return button
}()
private lazy var buttonsBar: ButtonsBar = {
let buttonsBar = ButtonsBar(configuration: .green(buttons: 1))
return buttonsBar
}()
private var viewModel: SwitchChainRequestViewModel
init(viewModel: SwitchChainRequestViewModel) {
self.viewModel = viewModel
super.init()
let footerView = ButtonsBarBackgroundView(buttonsBar: buttonsBar, separatorHeight: 0)
footerStackView.addArrangedSubview(footerView)
generateSubviews()
presentationDelegate = self
enableTestnetButton.addTarget(self, action: #selector(enableTestnetButtonSelected), for: .touchUpInside)
}
required init?(coder aDecoder: NSCoder) {
return nil
}
func configure(viewModel: SwitchChainRequestViewModel) {
self.viewModel = viewModel
generateSubviews()
buttonsBar.configure()
buttonsBar.buttons[0].setTitle(viewModel.actionButtonTitle, for: .normal)
buttonsBar.buttons[0].addTarget(self, action: #selector(actionButtonSelected), for: .touchUpInside)
titleLabel.text = viewModel.title
descriptionLabel.text = viewModel.description
}
@objc private func actionButtonSelected(_ sender: UIButton) {
dismissViewAnimated(with: {
self._delegate?.didSelectActionButton(in: self)
})
}
@objc private func enableTestnetButtonSelected(_ sender: UIButton) {
dismissViewAnimated(with: {
self._delegate?.didSelectAdditionalButton(in: self)
})
}
}
extension SwitchChainRequestViewController: ModalViewControllerDelegate {
func didDismiss(_ controller: ModalViewController) {
_delegate?.didClose(in: self)
dismiss(animated: false)
}
func didClose(_ controller: ModalViewController) {
dismissViewAnimated(with: {
self._delegate?.didClose(in: self)
self.dismiss(animated: false)
})
}
}
extension SwitchChainRequestViewController {
private func generateSubviews() {
stackView.removeAllArrangedSubviews()
var views: [UIView] = [
[.spacerWidth(16), titleLabel, .spacerWidth(16)].asStackView(axis: .horizontal),
.spacer(height: 20),
[.spacerWidth(16), descriptionLabel, .spacerWidth(16)].asStackView(axis: .horizontal),
]
switch viewModel.configuration {
case .promptAndSwitchToExistingServerInBrowser, .promptAndActivateExistingServer:
break
case .promptAndAddAndActivateServer:
views += [
.spacer(height: 20),
enableTestnetButton
]
}
stackView.addArrangedSubviews(views)
}
}
private class SwitchChainRequestViewControllerBridgeToPromise: NSObject {
private let (promiseToReturn, seal) = Promise<SwitchChainRequestResponse>.pending()
private var retainCycle: SwitchChainRequestViewControllerBridgeToPromise?
private let viewController: SwitchChainRequestViewController
init(viewController target: UIViewController, configuration: SwitchChainRequestConfiguration) {
viewController = SwitchChainRequestViewController(viewModel: .init(configuration: configuration))
viewController.configure(viewModel: .init(configuration: configuration))
super.init()
retainCycle = self
viewController._delegate = self
promiseToReturn.ensure {
// ensure we break the retain cycle
self.retainCycle = nil
}.cauterize()
target.present(viewController, animated: false)
}
var promise: Promise<SwitchChainRequestResponse> {
return promiseToReturn
}
}
extension SwitchChainRequestViewControllerBridgeToPromise: SwitchChainRequestViewControllerDelegate {
//NOTE: need to update it with more clear solution, passing button index isn't goo idea for that
func didSelectActionButton(in viewController: SwitchChainRequestViewController) {
seal.fulfill(.action(0))
}
func didSelectAdditionalButton(in viewController: SwitchChainRequestViewController) {
seal.fulfill(.action(1))
}
func didClose(in viewController: SwitchChainRequestViewController) {
seal.fulfill(.canceled)
}
}
extension SwitchChainRequestViewController {
static func promise(_ viewController: UIViewController, configuration: SwitchChainRequestConfiguration) -> Promise<SwitchChainRequestResponse> {
return SwitchChainRequestViewControllerBridgeToPromise(viewController: viewController, configuration: configuration).promise
}
}

@ -0,0 +1,53 @@
//
// SwitchChainRequestViewModel.swift
// AlphaWallet
//
// Created by Vladyslav Shepitko on 23.09.2021.
//
import UIKit
enum SwitchChainRequestConfiguration {
case promptAndSwitchToExistingServerInBrowser(existingServer: RPCServer)
case promptAndAddAndActivateServer(customChain: WalletAddEthereumChainObject, customChainId: Int)
case promptAndActivateExistingServer(existingServer: RPCServer)
}
enum SwitchChainRequestResponse {
case action(Int)
case canceled
}
struct SwitchChainRequestViewModel {
let title: String = R.string.localizable.switchChainRequestTitle()
let configuration: SwitchChainRequestConfiguration
var description: String {
switch configuration {
case .promptAndSwitchToExistingServerInBrowser(let existingServer):
return R.string.localizable.addCustomChainSwitchToExisting(existingServer.displayName, existingServer.chainID)
case .promptAndAddAndActivateServer(let customChain, let customChainId):
return R.string.localizable.addCustomChainAddAndSwitch(customChain.chainName ?? R.string.localizable.addCustomChainUnnamed(), customChainId)
case .promptAndActivateExistingServer(let existingServer):
return R.string.localizable.addCustomChainEnableExisting(existingServer.displayName, existingServer.chainID)
}
}
var actionButtonTitle: String {
switch configuration {
case .promptAndSwitchToExistingServerInBrowser:
// Switch & Reload
return R.string.localizable.switchChainRequestActionSwitchReload()
case .promptAndAddAndActivateServer:
// Add, Switch & Reload Mainnet
return R.string.localizable.switchChainRequestActionAddSwitchReload(R.string.localizable.settingsEnabledNetworksMainnet())
case .promptAndActivateExistingServer:
// Enable, Switch & Reload
return R.string.localizable.switchChainRequestActionEnableSwitchReload()
}
}
var additionalButtonTitle: String {
R.string.localizable.switchChainRequestActionAddSwitchReload(R.string.localizable.settingsEnabledNetworksTestnet())
}
}

@ -674,3 +674,7 @@ You can check the latest gas price on gasnow.org";
"sortTokens.param.valueDescending" = "Value: Descending";
"sortTokens.param.mostUsed" = "Most Used";
"sortTokens.sortBy" = "Sort: By %@";
"switchChainRequest.title" = "Switch Chain Request";
"switchChainRequest.action.enableSwitchReload" = "Enable, Switch & Reload";
"switchChainRequest.action.switchReload" = "Switch & Reload";
"switchChainRequest.action.addSwitchReload" = "Add, Switch & Reload %@";

@ -674,3 +674,7 @@ You can check the latest gas price on gasnow.org";
"sortTokens.param.valueDescending" = "Value: Descending";
"sortTokens.param.mostUsed" = "Most Used";
"sortTokens.sortBy" = "Sort: By %@";
"switchChainRequest.title" = "Switch Chain Request";
"switchChainRequest.action.enableSwitchReload" = "Enable, Switch & Reload";
"switchChainRequest.action.switchReload" = "Switch & Reload";
"switchChainRequest.action.addSwitchReload" = "Add, Switch & Reload %@";

@ -674,3 +674,7 @@ You can check the latest gas price on gasnow.org";
"sortTokens.param.valueDescending" = "Value: Descending";
"sortTokens.param.mostUsed" = "Most Used";
"sortTokens.sortBy" = "Sort: By %@";
"switchChainRequest.title" = "Switch Chain Request";
"switchChainRequest.action.enableSwitchReload" = "Enable, Switch & Reload";
"switchChainRequest.action.switchReload" = "Switch & Reload";
"switchChainRequest.action.addSwitchReload" = "Add, Switch & Reload %@";

@ -674,3 +674,7 @@ You can check the latest gas price on gasnow.org";
"sortTokens.param.valueDescending" = "Value: Descending";
"sortTokens.param.mostUsed" = "Most Used";
"sortTokens.sortBy" = "Sort: By %@";
"switchChainRequest.title" = "Switch Chain Request";
"switchChainRequest.action.enableSwitchReload" = "Enable, Switch & Reload";
"switchChainRequest.action.switchReload" = "Switch & Reload";
"switchChainRequest.action.addSwitchReload" = "Add, Switch & Reload %@";

@ -674,3 +674,7 @@ You can check the latest gas price on gasnow.org";
"sortTokens.param.valueDescending" = "Value: Descending";
"sortTokens.param.mostUsed" = "Most Used";
"sortTokens.sortBy" = "Sort: By %@";
"switchChainRequest.title" = "Switch Chain Request";
"switchChainRequest.action.enableSwitchReload" = "Enable, Switch & Reload";
"switchChainRequest.action.switchReload" = "Switch & Reload";
"switchChainRequest.action.addSwitchReload" = "Add, Switch & Reload %@";

@ -87,7 +87,7 @@ extension AddRPCServerCoordinator: AddCustomChainDelegate {
extension UIAlertController {
static func promptToUseUnresolvedExplorerURL(customChain: WalletAddEthereumChainObject, chainId: Int, viewController: UIViewController) -> Promise<Bool> {
let (promise, seal) = Promise<Bool>.pending()
let message = R.string.localizable.addCustomChainWarningNoBlockchainExplorerUrl(customChain.chainName ?? "-")
let message = R.string.localizable.addCustomChainWarningNoBlockchainExplorerUrl()
let alertController = UIAlertController.alertController(title: R.string.localizable.warning(), message: message, style: .alert, in: viewController)
let continueAction = UIAlertAction(title: R.string.localizable.continue(), style: .destructive, handler: { _ in
seal.fulfill(true)

Loading…
Cancel
Save