Allow users to paste a public key instead of scanning a QR code

pull/70/head
Niels Andriesse 6 years ago
parent 734835b4ac
commit 56b0916a1b

@ -6,7 +6,7 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
// MARK: Components // MARK: Components
private lazy var registerStackView: UIStackView = { private lazy var registerStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ explanationLabel1, UIView.spacer(withHeight: 32), mnemonicLabel, UIView.spacer(withHeight: 24), copyButton, restoreButton, linkButton1 ]) let result = UIStackView(arrangedSubviews: [ explanationLabel1, UIView.spacer(withHeight: 32), mnemonicLabel, UIView.spacer(withHeight: 24), copyButton, restoreButton1, linkButton1 ])
result.accessibilityIdentifier = "onboarding.keyPairStep.registerStackView" result.accessibilityIdentifier = "onboarding.keyPairStep.registerStackView"
result.axis = .vertical result.axis = .vertical
return result return result
@ -39,22 +39,22 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
return result return result
}() }()
private lazy var restoreButton: OWSFlatButton = { private lazy var restoreButton1: OWSFlatButton = {
let result = createLinkButton(title: NSLocalizedString("Restore Using Seed", comment: ""), selector: #selector(handleSwitchModeButton1Tapped)) let result = createLinkButton(title: NSLocalizedString("Restore Using Seed", comment: ""), selector: #selector(handleSwitchModeButton1Tapped))
result.accessibilityIdentifier = "onboarding.keyPairStep.restoreButton" result.accessibilityIdentifier = "onboarding.keyPairStep.restoreButton1"
result.setBackgroundColors(upColor: .clear, downColor: .clear) result.setBackgroundColors(upColor: .clear, downColor: .clear)
return result return result
}() }()
private lazy var linkButton1: OWSFlatButton = { private lazy var linkButton1: OWSFlatButton = {
let result = createLinkButton(title: NSLocalizedString("Link Device", comment: ""), selector: #selector(handleLinkButtonTapped)) let result = createLinkButton(title: NSLocalizedString("Link Device", comment: ""), selector: #selector(handleSwitchModeButton2Tapped))
result.accessibilityIdentifier = "onboarding.keyPairStep.linkButton1" result.accessibilityIdentifier = "onboarding.keyPairStep.linkButton1"
result.setBackgroundColors(upColor: .clear, downColor: .clear) result.setBackgroundColors(upColor: .clear, downColor: .clear)
return result return result
}() }()
private lazy var restoreStackView: UIStackView = { private lazy var restoreStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ explanationLabel2, UIView.spacer(withHeight: 32), errorLabel, errorLabelSpacer, mnemonicTextField, UIView.spacer(withHeight: 24), registerButton, linkButton2 ]) let result = UIStackView(arrangedSubviews: [ explanationLabel2, UIView.spacer(withHeight: 32), errorLabel1, errorLabel1Spacer, mnemonicTextField, UIView.spacer(withHeight: 24), registerButton1, linkButton2 ])
result.accessibilityIdentifier = "onboarding.keyPairStep.restoreStackView" result.accessibilityIdentifier = "onboarding.keyPairStep.restoreStackView"
result.axis = .vertical result.axis = .vertical
return result return result
@ -70,9 +70,9 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
return result return result
}() }()
private lazy var errorLabel: UILabel = { private lazy var errorLabel1: UILabel = {
let result = createExplanationLabel(text: "") let result = createExplanationLabel(text: "")
result.accessibilityIdentifier = "onboarding.keyPairStep.errorLabel" result.accessibilityIdentifier = "onboarding.keyPairStep.errorLabel1"
result.textColor = UIColor.red result.textColor = UIColor.red
var fontTraits = result.font.fontDescriptor.symbolicTraits var fontTraits = result.font.fontDescriptor.symbolicTraits
fontTraits.insert(.traitBold) fontTraits.insert(.traitBold)
@ -80,7 +80,7 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
return result return result
}() }()
private lazy var errorLabelSpacer: UIView = { private lazy var errorLabel1Spacer: UIView = {
let result = UIView.spacer(withHeight: 32) let result = UIView.spacer(withHeight: 32)
result.isHidden = true result.isHidden = true
return result return result
@ -100,20 +100,81 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
return result return result
}() }()
private lazy var registerButton: OWSFlatButton = { private lazy var registerButton1: OWSFlatButton = {
let result = createLinkButton(title: NSLocalizedString("Register a New Account", comment: ""), selector: #selector(handleSwitchModeButton1Tapped)) let result = createLinkButton(title: NSLocalizedString("Register a New Account", comment: ""), selector: #selector(handleSwitchModeButton1Tapped))
result.accessibilityIdentifier = "onboarding.keyPairStep.registerButton" result.accessibilityIdentifier = "onboarding.keyPairStep.registerButton1"
result.setBackgroundColors(upColor: .clear, downColor: .clear) result.setBackgroundColors(upColor: .clear, downColor: .clear)
return result return result
}() }()
private lazy var linkButton2: OWSFlatButton = { private lazy var linkButton2: OWSFlatButton = {
let result = createLinkButton(title: NSLocalizedString("Link Device", comment: ""), selector: #selector(handleLinkButtonTapped)) let result = createLinkButton(title: NSLocalizedString("Link Device", comment: ""), selector: #selector(handleSwitchModeButton2Tapped))
result.accessibilityIdentifier = "onboarding.keyPairStep.linkButton2" result.accessibilityIdentifier = "onboarding.keyPairStep.linkButton2"
result.setBackgroundColors(upColor: .clear, downColor: .clear) result.setBackgroundColors(upColor: .clear, downColor: .clear)
return result return result
}() }()
private lazy var linkStackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ explanationLabel3, UIView.spacer(withHeight: 32), errorLabel2, errorLabel2Spacer, masterHexEncodedPublicKeyTextField, UIView.spacer(withHeight: 24), registerButton2, restoreButton2 ])
result.accessibilityIdentifier = "onboarding.keyPairStep.linkStackView"
result.axis = .vertical
return result
}()
private lazy var explanationLabel3: UILabel = {
let result = createExplanationLabel(text: NSLocalizedString("Link to an existing device by going into its in-app settings and clicking \"Link Device\".", comment: ""))
result.accessibilityIdentifier = "onboarding.keyPairStep.explanationLabel3"
result.textColor = Theme.primaryColor
var fontTraits = result.font.fontDescriptor.symbolicTraits
fontTraits.insert(.traitBold)
result.font = UIFont(descriptor: result.font.fontDescriptor.withSymbolicTraits(fontTraits)!, size: result.font.pointSize)
return result
}()
private lazy var errorLabel2: UILabel = {
let result = createExplanationLabel(text: "")
result.accessibilityIdentifier = "onboarding.keyPairStep.errorLabel2"
result.textColor = UIColor.red
var fontTraits = result.font.fontDescriptor.symbolicTraits
fontTraits.insert(.traitBold)
result.font = UIFont(descriptor: result.font.fontDescriptor.withSymbolicTraits(fontTraits)!, size: 12)
return result
}()
private lazy var errorLabel2Spacer: UIView = {
let result = UIView.spacer(withHeight: 32)
result.isHidden = true
return result
}()
private lazy var masterHexEncodedPublicKeyTextField: UITextField = {
let result = UITextField(frame: CGRect.zero)
result.textColor = Theme.primaryColor
result.font = UIFont.ows_dynamicTypeBodyClamped
result.textAlignment = .center
let placeholder = NSMutableAttributedString(string: NSLocalizedString("Enter the Other Device's Public Key", comment: ""))
placeholder.addAttribute(.foregroundColor, value: Theme.placeholderColor, range: NSRange(location: 0, length: placeholder.length))
result.attributedPlaceholder = placeholder
result.tintColor = UIColor.lokiGreen()
result.accessibilityIdentifier = "onboarding.keyPairStep.masterHexEncodedPublicKeyTextField"
result.keyboardAppearance = .dark
return result
}()
private lazy var registerButton2: OWSFlatButton = {
let result = createLinkButton(title: NSLocalizedString("Register a New Account", comment: ""), selector: #selector(handleSwitchModeButton1Tapped))
result.accessibilityIdentifier = "onboarding.keyPairStep.registerButton2"
result.setBackgroundColors(upColor: .clear, downColor: .clear)
return result
}()
private lazy var restoreButton2: OWSFlatButton = {
let result = createLinkButton(title: NSLocalizedString("Restore Using Seed", comment: ""), selector: #selector(handleSwitchModeButton2Tapped))
result.accessibilityIdentifier = "onboarding.keyPairStep.restoreButton2"
result.setBackgroundColors(upColor: .clear, downColor: .clear)
return result
}()
private lazy var mainButton: OWSFlatButton = { private lazy var mainButton: OWSFlatButton = {
let result = createButton(title: "", selector: #selector(objc_proceed)) let result = createButton(title: "", selector: #selector(objc_proceed))
result.accessibilityIdentifier = "onboarding.keyPairStep.mainButton" result.accessibilityIdentifier = "onboarding.keyPairStep.mainButton"
@ -121,7 +182,7 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
}() }()
// MARK: Types // MARK: Types
enum Mode { case register, restore } enum Mode { case register, restore, link }
// MARK: Lifecycle // MARK: Lifecycle
override func viewDidLoad() { override func viewDidLoad() {
@ -143,6 +204,7 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
let mainView = UIView(frame: CGRect.zero) let mainView = UIView(frame: CGRect.zero)
mainView.addSubview(restoreStackView) mainView.addSubview(restoreStackView)
mainView.addSubview(registerStackView) mainView.addSubview(registerStackView)
mainView.addSubview(linkStackView)
let mainStackView = UIStackView(arrangedSubviews: [ titleLabel, mainView, mainButton ]) let mainStackView = UIStackView(arrangedSubviews: [ titleLabel, mainView, mainButton ])
mainStackView.axis = .vertical mainStackView.axis = .vertical
mainStackView.layoutMargins = UIEdgeInsets(top: 32, left: 32, bottom: 32, right: 32) mainStackView.layoutMargins = UIEdgeInsets(top: 32, left: 32, bottom: 32, right: 32)
@ -156,6 +218,8 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
registerStackView.autoVCenterInSuperview() registerStackView.autoVCenterInSuperview()
restoreStackView.autoPinWidthToSuperview() restoreStackView.autoPinWidthToSuperview()
restoreStackView.autoVCenterInSuperview() restoreStackView.autoVCenterInSuperview()
linkStackView.autoPinWidthToSuperview()
linkStackView.autoVCenterInSuperview()
} }
// MARK: General // MARK: General
@ -170,8 +234,9 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
private func handleModeChanged() { private func handleModeChanged() {
let (activeStackView, otherStackViews) = { () -> (UIStackView, [UIStackView]) in let (activeStackView, otherStackViews) = { () -> (UIStackView, [UIStackView]) in
switch mode { switch mode {
case .register: return (registerStackView, [ restoreStackView ]) case .register: return (registerStackView, [ restoreStackView, linkStackView ])
case .restore: return (restoreStackView, [ registerStackView ]) case .restore: return (restoreStackView, [ registerStackView, linkStackView ])
case .link: return (linkStackView, [ registerStackView, restoreStackView ])
} }
}() }()
UIView.animate(withDuration: 0.25) { UIView.animate(withDuration: 0.25) {
@ -182,12 +247,29 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
switch mode { switch mode {
case .register: return NSLocalizedString("Register", comment: "") case .register: return NSLocalizedString("Register", comment: "")
case .restore: return NSLocalizedString("Restore", comment: "") case .restore: return NSLocalizedString("Restore", comment: "")
case .link: return NSLocalizedString("Link", comment: "")
} }
}() }()
UIView.transition(with: mainButton, duration: 0.25, options: .transitionCrossDissolve, animations: { UIView.transition(with: mainButton, duration: 0.25, options: .transitionCrossDissolve, animations: {
self.mainButton.setTitle(mainButtonTitle) self.mainButton.setTitle(mainButtonTitle)
}, completion: nil) }, completion: nil)
if mode != .restore { mnemonicTextField.resignFirstResponder() } if mode != .restore { mnemonicTextField.resignFirstResponder() }
if mode != .link { masterHexEncodedPublicKeyTextField.resignFirstResponder() }
if mode == .link {
ows_ask(forCameraPermissions: { [weak self] hasCameraAccess in
guard let self = self else { return }
if hasCameraAccess {
let message = NSLocalizedString("Link to an existing device by going into its in-app settings and clicking \"Link Device\".", comment: "")
let scanQRCodeWrapperVC = ScanQRCodeWrapperVC(message: message)
scanQRCodeWrapperVC.delegate = self
scanQRCodeWrapperVC.isPresentedModally = true
let navigationVC = OWSNavigationController(rootViewController: scanQRCodeWrapperVC)
self.present(navigationVC, animated: true, completion: nil)
} else {
// Do nothing
}
})
}
} }
private func updateSeed() { private func updateSeed() {
@ -217,23 +299,16 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
switch mode { switch mode {
case .register: mode = .restore case .register: mode = .restore
case .restore: mode = .register case .restore: mode = .register
case .link: mode = .register
} }
} }
@objc private func handleLinkButtonTapped() { @objc private func handleSwitchModeButton2Tapped() {
ows_ask(forCameraPermissions: { [weak self] hasCameraAccess in switch mode {
guard let self = self else { return } case .register: mode = .link
if hasCameraAccess { case .restore: mode = .link
let message = NSLocalizedString("Link to an existing device by going into its in-app settings and clicking \"Link Device\".", comment: "") case .link: mode = .restore
let scanQRCodeWrapperVC = ScanQRCodeWrapperVC(message: message) }
scanQRCodeWrapperVC.delegate = self
scanQRCodeWrapperVC.isPresentedModally = true
let navigationVC = OWSNavigationController(rootViewController: scanQRCodeWrapperVC)
self.present(navigationVC, animated: true, completion: nil)
} else {
// Do nothing
}
})
} }
func controller(_ controller: OWSQRCodeScanningViewController, didDetectQRCodeWith string: String) { func controller(_ controller: OWSQRCodeScanningViewController, didDetectQRCodeWith string: String) {
@ -249,27 +324,31 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
private func proceed(with masterHexEncodedPublicKey: String? = nil) { private func proceed(with masterHexEncodedPublicKey: String? = nil) {
var seed: Data var seed: Data
if let masterHexEncodedPublicKey = masterHexEncodedPublicKey { let mode = self.mode
switch mode {
case .register: seed = self.seed
case .restore:
let mnemonic = mnemonicTextField.text!
do {
let hexEncodedSeed = try Mnemonic.decode(mnemonic: mnemonic)
seed = Data(hex: hexEncodedSeed)
} catch let error {
let error = error as? Mnemonic.DecodingError ?? Mnemonic.DecodingError.generic
errorLabel1Spacer.isHidden = false
return errorLabel1.text = error.errorDescription
}
case .link:
seed = self.seed seed = self.seed
let isUsingQRCode = masterHexEncodedPublicKey != nil
let masterHexEncodedPublicKey = masterHexEncodedPublicKey ?? masterHexEncodedPublicKeyTextField.text!.trimmingCharacters(in: CharacterSet.whitespaces)
if !ECKeyPair.isValidHexEncodedPublicKey(candidate: masterHexEncodedPublicKey) { if !ECKeyPair.isValidHexEncodedPublicKey(candidate: masterHexEncodedPublicKey) {
let alert = UIAlertController(title: NSLocalizedString("Invalid QR Code", comment: ""), message: NSLocalizedString("Please make sure the QR code you scanned is correct and try again.", comment: ""), preferredStyle: .alert) if isUsingQRCode {
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), accessibilityIdentifier: nil, style: .default, handler: nil)) let alert = UIAlertController(title: NSLocalizedString("Invalid QR Code", comment: ""), message: NSLocalizedString("Please make sure the QR code you scanned is correct and try again.", comment: ""), preferredStyle: .alert)
return present(alert, animated: true, completion: nil) alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), accessibilityIdentifier: nil, style: .default, handler: nil))
} return present(alert, animated: true, completion: nil)
Analytics.shared.track("Device Linking Attempted") } else {
} else { errorLabel2Spacer.isHidden = false
let mode = self.mode return errorLabel2.text = NSLocalizedString("Invalid public key", comment: "")
switch mode {
case .register: seed = self.seed
case .restore:
let mnemonic = mnemonicTextField.text!
do {
let hexEncodedSeed = try Mnemonic.decode(mnemonic: mnemonic)
seed = Data(hex: hexEncodedSeed)
} catch let error {
let error = error as? Mnemonic.DecodingError ?? Mnemonic.DecodingError.generic
errorLabelSpacer.isHidden = false
return errorLabel.text = error.errorDescription
} }
} }
} }
@ -286,8 +365,11 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
switch mode { switch mode {
case .register: Analytics.shared.track("Seed Created") case .register: Analytics.shared.track("Seed Created")
case .restore: Analytics.shared.track("Seed Restored") case .restore: Analytics.shared.track("Seed Restored")
case .link: Analytics.shared.track("Device Linking Attempted")
} }
if let masterHexEncodedPublicKey = masterHexEncodedPublicKey { if mode == .link {
let isUsingQRCode = masterHexEncodedPublicKey != nil
let masterHexEncodedPublicKey = masterHexEncodedPublicKey ?? masterHexEncodedPublicKeyTextField.text!.trimmingCharacters(in: CharacterSet.whitespaces)
TSAccountManager.sharedInstance().didRegister() TSAccountManager.sharedInstance().didRegister()
setUserInteractionEnabled(false) setUserInteractionEnabled(false)
let _ = LokiStorageAPI.getDeviceLinks(associatedWith: masterHexEncodedPublicKey).done(on: DispatchQueue.main) { [weak self] deviceLinks in let _ = LokiStorageAPI.getDeviceLinks(associatedWith: masterHexEncodedPublicKey).done(on: DispatchQueue.main) { [weak self] deviceLinks in
@ -338,7 +420,7 @@ final class SeedVC : OnboardingBaseViewController, DeviceLinkingModalDelegate, O
// MARK: Convenience // MARK: Convenience
private func setUserInteractionEnabled(_ isEnabled: Bool) { private func setUserInteractionEnabled(_ isEnabled: Bool) {
[ copyButton, restoreButton, linkButton1, registerButton, linkButton2, mainButton ].forEach { [ copyButton, restoreButton1, linkButton1, registerButton1, linkButton2, registerButton2, restoreButton2, mainButton ].forEach {
$0.isUserInteractionEnabled = isEnabled $0.isUserInteractionEnabled = isEnabled
} }
} }

Loading…
Cancel
Save