Skip to content

Commit

Permalink
Merge pull request #1145 from MixinNetwork/feature/tip
Browse files Browse the repository at this point in the history
Test AmountFormatter
  • Loading branch information
over140 authored Oct 18, 2022
2 parents abd845e + 762fe11 commit 1092074
Show file tree
Hide file tree
Showing 13 changed files with 242 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ class LoneBackButtonNavigationController: UINavigationController {
return super.popToRootViewController(animated: animated)
}

override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) {
defer {
updateBackButtonAlpha(animated: animated)
}
super.setViewControllers(viewControllers, animated: animated)
}

@objc func backAction(sender: Any) {
popViewController(animated: true)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ class TIPDiagnosticViewController: SettingsTableViewController {
private let dataSource = SettingsDataSource(sections: [
SettingsSection(header: "Failure Tests", rows: [
SettingsRow(title: "Fail Last Sign Once", accessory: .switch(isOn: TIPDiagnostic.failLastSignerOnce, isEnabled: true)),
SettingsRow(title: "Fail PIN Update Once", accessory: .switch(isOn: TIPDiagnostic.failPINUpdateOnce, isEnabled: true)),
SettingsRow(title: "Fail PIN Update Server Once", accessory: .switch(isOn: TIPDiagnostic.failPINUpdateServerSideOnce, isEnabled: true)),
SettingsRow(title: "Fail PIN Update Client Once", accessory: .switch(isOn: TIPDiagnostic.failPINUpdateClientSideOnce, isEnabled: true)),
SettingsRow(title: "Fail Watch Once", accessory: .switch(isOn: TIPDiagnostic.failCounterWatchOnce, isEnabled: true)),
SettingsRow(title: "Crash After PIN Update", accessory: .switch(isOn: TIPDiagnostic.crashAfterUpdatePIN, isEnabled: true)),
SettingsRow(title: "Invalid Nonce Once", accessory: .switch(isOn: TIPDiagnostic.invalidNonceOnce, isEnabled: true)),
]),
SettingsSection(header: "UI Test", rows: [
SettingsRow(title: "UI Test On", accessory: .switch(isOn: TIPDiagnostic.uiTestOnly, isEnabled: true)),
Expand Down Expand Up @@ -37,11 +39,15 @@ class TIPDiagnosticViewController: SettingsTableViewController {
case dataSource.sections[0].rows[0]:
TIPDiagnostic.failLastSignerOnce.toggle()
case dataSource.sections[0].rows[1]:
TIPDiagnostic.failPINUpdateOnce.toggle()
TIPDiagnostic.failPINUpdateServerSideOnce.toggle()
case dataSource.sections[0].rows[2]:
TIPDiagnostic.failCounterWatchOnce.toggle()
TIPDiagnostic.failPINUpdateClientSideOnce.toggle()
case dataSource.sections[0].rows[3]:
TIPDiagnostic.failCounterWatchOnce.toggle()
case dataSource.sections[0].rows[4]:
TIPDiagnostic.crashAfterUpdatePIN.toggle()
case dataSource.sections[0].rows[5]:
TIPDiagnostic.invalidNonceOnce.toggle()
case dataSource.sections[1].rows[0]:
TIPDiagnostic.uiTestOnly.toggle()
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class TIPActionViewController: UIViewController {
}

private func performAction() {
guard let accountCounterBefore = LoginManager.shared.account?.tipCounter else {
return
}
switch action {
case let .create(pin):
titleLabel.text = R.string.localizable.create_pin()
Expand All @@ -84,7 +87,7 @@ class TIPActionViewController: UIViewController {
finish()
}
} catch {
await handle(error: error)
await handle(error: error, accountCounterBefore: accountCounterBefore)
}
}
case let .change(old, new):
Expand Down Expand Up @@ -119,7 +122,7 @@ class TIPActionViewController: UIViewController {
finish()
}
} catch {
await handle(error: error)
await handle(error: error, accountCounterBefore: accountCounterBefore)
}
}
case let .migrate(pin):
Expand All @@ -142,7 +145,7 @@ class TIPActionViewController: UIViewController {
finish()
}
} catch {
await handle(error: error)
await handle(error: error, accountCounterBefore: accountCounterBefore)
}
}
}
Expand All @@ -165,22 +168,28 @@ class TIPActionViewController: UIViewController {
}
}

private func handle(error: Error) async {
private func handle(error: Error, accountCounterBefore: UInt64) async {
Logger.tip.error(category: "TIPAction", message: "Failed with: \(error)")
do {
guard let account = LoginManager.shared.account else {
return
}
guard let context = try await TIP.checkCounter(with: account) else {
if let context = try await TIP.checkCounter() {
await MainActor.run {
Logger.tip.error(category: "TIPAction", message: "No interruption is detected")
finish()
let intro = TIPIntroViewController(context: context)
navigationController?.setViewControllers([intro], animated: true)
}
} else {
try await MainActor.run {
guard let accountCounterAfter = LoginManager.shared.account?.tipCounter else {
throw MixinAPIError.unauthorized
}
if accountCounterAfter == accountCounterBefore {
Logger.tip.error(category: "TIPAction", message: "Nothing changed")
let intro = TIPIntroViewController(action: action, changedNothingWith: error)
tipNavigationController?.setViewControllers([intro], animated: true)
} else {
Logger.tip.warn(category: "TIPAction", message: "No interruption is detected")
finish()
}
}
return
}
await MainActor.run {
let intro = TIPIntroViewController(context: context)
navigationController?.setViewControllers([intro], animated: true)
}
} catch {
await MainActor.run {
Expand Down Expand Up @@ -234,7 +243,7 @@ class TIPActionViewController: UIViewController {
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 13) {
if TIPDiagnostic.failLastSignerOnce || TIPDiagnostic.failPINUpdateOnce {
if TIPDiagnostic.failLastSignerOnce || TIPDiagnostic.failPINUpdateServerSideOnce {
let action: TIP.Action
switch self.action {
case .create:
Expand All @@ -249,8 +258,8 @@ class TIPActionViewController: UIViewController {
if TIPDiagnostic.failLastSignerOnce {
TIPDiagnostic.failLastSignerOnce = false
situation = .pendingSign([])
} else if TIPDiagnostic.failPINUpdateOnce {
TIPDiagnostic.failPINUpdateOnce = false
} else if TIPDiagnostic.failPINUpdateServerSideOnce {
TIPDiagnostic.failPINUpdateServerSideOnce = false
situation = .pendingUpdate
} else {
fatalError()
Expand Down
81 changes: 58 additions & 23 deletions Mixin/UserInterface/Controllers/Wallet/TIPIntroViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ class TIPIntroViewController: UIViewController {
enum Interruption {
case unknown
case none
case confirmed(TIP.InterruptionContext)
case inputNeeded(TIP.InterruptionContext)
case noInputNeeded(TIPActionViewController.Action, Error)
}

private enum Status {
Expand All @@ -26,6 +27,15 @@ class TIPIntroViewController: UIViewController {

@IBOutlet weak var noticeTextViewHeightConstraint: NSLayoutConstraint!

var isDismissAllowed: Bool {
switch interruption {
case .none, .noInputNeeded:
return true
case .unknown, .inputNeeded:
return false
}
}

private let intent: TIP.Action
private let checkCounterTimeoutInterval: TimeInterval = 5

Expand All @@ -35,26 +45,41 @@ class TIPIntroViewController: UIViewController {
navigationController as? TIPNavigationViewController
}

init(intent: TIP.Action) {
convenience init(intent: TIP.Action) {
Logger.tip.info(category: "TIPIntro", message: "Init with intent: \(intent)")
self.intent = intent
self.interruption = .unknown
let nib = R.nib.tipIntroView
super.init(nibName: nib.name, bundle: nib.bundle)
self.init(intent: intent, interruption: .unknown)
}

init(context: TIP.InterruptionContext) {
convenience init(context: TIP.InterruptionContext) {
Logger.tip.info(category: "TIPIntro", message: "Init with context: \(context)")
self.intent = context.action
self.interruption = .confirmed(context)
let nib = R.nib.tipIntroView
super.init(nibName: nib.name, bundle: nib.bundle)
self.init(intent: context.action, interruption: .inputNeeded(context))
}

convenience init(action: TIPActionViewController.Action, changedNothingWith error: Error) {
Logger.tip.info(category: "TIPIntro", message: "Init with action: \(action.debugDescription), error: \(error)")
let intent: TIP.Action
switch action {
case .create:
intent = .create
case .change:
intent = .change
case .migrate:
intent = .migrate
}
self.init(intent: intent, interruption: .noInputNeeded(action, error))
}

required init?(coder: NSCoder) {
fatalError("Storyboard is not supported")
}

private init(intent: TIP.Action, interruption: Interruption) {
self.intent = intent
self.interruption = interruption
let nib = R.nib.tipIntroView
super.init(nibName: nib.name, bundle: nib.bundle)
}

override func viewDidLoad() {
super.viewDidLoad()
contentStackView.setCustomSpacing(24, after: iconImageView)
Expand All @@ -67,7 +92,7 @@ class TIPIntroViewController: UIViewController {
switch interruption {
case .unknown, .none:
description = R.string.localizable.tip_creation_introduction()
case .confirmed:
case .inputNeeded, .noInputNeeded:
description = R.string.localizable.creating_wallet_terminated_unexpectedly()
}
setNoticeHidden(false)
Expand All @@ -76,7 +101,7 @@ class TIPIntroViewController: UIViewController {
switch interruption {
case .unknown, .none:
description = R.string.localizable.tip_introduction()
case .confirmed:
case .inputNeeded, .noInputNeeded:
description = R.string.localizable.changing_pin_terminated_unexpectedly()
}
setNoticeHidden(false)
Expand All @@ -85,7 +110,7 @@ class TIPIntroViewController: UIViewController {
switch interruption {
case .unknown, .none:
description = R.string.localizable.tip_introduction()
case .confirmed:
case .inputNeeded, .noInputNeeded:
description = R.string.localizable.upgrading_tip_terminated_unexpectedly()
}
setNoticeHidden(false)
Expand All @@ -103,11 +128,11 @@ class TIPIntroViewController: UIViewController {
case .unknown:
checkCounter()
descriptionTextLabel.additionalLinksMap = linksMap
case .confirmed:
updateNextButtonAndStatusLabel(with: .waitingForUser)
case .none:
descriptionTextLabel.additionalLinksMap = linksMap
updateNextButtonAndStatusLabel(with: .waitingForUser)
case .inputNeeded, .noInputNeeded:
updateNextButtonAndStatusLabel(with: .waitingForUser)
}
}

Expand Down Expand Up @@ -148,7 +173,7 @@ class TIPIntroViewController: UIViewController {
}))
present(validator, animated: true)
}
case .confirmed(let context):
case .inputNeeded(let context):
switch context.action {
case .migrate:
let validator = TIPPopupInputViewController(action: .migrate({ pin in
Expand All @@ -165,6 +190,9 @@ class TIPIntroViewController: UIViewController {
}))
present(validator, animated: true)
}
case let .noInputNeeded(action, _):
let viewController = TIPActionViewController(action: action)
navigationController?.setViewControllers([viewController], animated: true)
}
}

Expand Down Expand Up @@ -217,21 +245,19 @@ extension TIPIntroViewController {
}

private func checkCounter() {
guard let account = LoginManager.shared.account else {
return
}
updateNextButtonAndStatusLabel(with: .checkingCounter)
Logger.tip.info(category: "TIPIntro", message: "Checking counter")
Task {
do {
let context = try await TIP.checkCounter(with: account, timeoutInterval: checkCounterTimeoutInterval)
let context = try await TIP.checkCounter(timeoutInterval: checkCounterTimeoutInterval)
await MainActor.run {
Logger.tip.info(category: "TIPIntro", message: "Got context: \(String(describing: context))")
if let context = context {
let intro = TIPIntroViewController(context: context)
navigationController?.setViewControllers([intro], animated: true)
} else {
interruption = .none
tipNavigationController?.updateBackButtonAlpha(animated: true)
updateNextButtonAndStatusLabel(with: .waitingForUser)
}
}
Expand Down Expand Up @@ -260,11 +286,20 @@ extension TIPIntroViewController {
switch interruption {
case .unknown, .none:
setNextButtonTitleByIntent()
case .confirmed:
actionDescriptionLabel.text = nil
case .inputNeeded:
nextButton.setTitle(R.string.localizable.continue(), for: .normal)
actionDescriptionLabel.text = nil
case let .noInputNeeded(_, error):
nextButton.setTitle(R.string.localizable.retry(), for: .normal)
if let error = error as? TIPNode.Error {
actionDescriptionLabel.text = error.description
} else {
actionDescriptionLabel.text = error.localizedDescription
}
actionDescriptionLabel.textColor = .mixinRed
}
nextButton.isBusy = false
actionDescriptionLabel.text = nil
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,9 @@ class TIPNavigationViewController: LoneBackButtonNavigationController {
if viewControllers.last is TIPActionViewController {
backButtonAlpha = 0
dismissButtonAlpha = 0
} else if viewControllers.last is TIPIntroViewController {
} else if let intro = viewControllers.last as? TIPIntroViewController {
backButtonAlpha = 0
dismissButtonAlpha = 1
dismissButtonAlpha = intro.isDismissAllowed ? 1 : 0
} else {
backButtonAlpha = 1
dismissButtonAlpha = 0
Expand Down
4 changes: 2 additions & 2 deletions Mixin/UserInterface/Windows/UrlWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ class UrlWindow {
guard let assetId = query["asset"], let amount = query["amount"], let traceId = query["trace"], let addressId = query["address"] else {
return false
}
guard !assetId.isEmpty && UUID(uuidString: assetId) != nil && !traceId.isEmpty && UUID(uuidString: traceId) != nil && !addressId.isEmpty && UUID(uuidString: addressId) != nil && !amount.isEmpty else {
guard !assetId.isEmpty && UUID(uuidString: assetId) != nil && !traceId.isEmpty && UUID(uuidString: traceId) != nil && !addressId.isEmpty && UUID(uuidString: addressId) != nil && !amount.isEmpty && AmountFormatter.isValid(amount) else {
return false
}
var memo = query["memo"]
Expand Down Expand Up @@ -484,7 +484,7 @@ class UrlWindow {
showAutoHiddenHud(style: .error, text: R.string.localizable.invalid_payment_link())
return true
}
guard !recipientId.isEmpty && UUID(uuidString: recipientId) != nil && !assetId.isEmpty && UUID(uuidString: assetId) != nil && !amount.isEmpty && amount.isGenericNumber else {
guard !recipientId.isEmpty && UUID(uuidString: recipientId) != nil && !assetId.isEmpty && UUID(uuidString: assetId) != nil && !amount.isEmpty && amount.isGenericNumber && AmountFormatter.isValid(amount) else {
Logger.general.error(category: "PayURL", message: "Invalid URL: \(url)")
showAutoHiddenHud(style: .error, text: R.string.localizable.invalid_payment_link())
return true
Expand Down
11 changes: 11 additions & 0 deletions MixinServices/MixinServices/Foundation/AmountFormatter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,15 @@ public enum AmountFormatter {
}
}

public static func isValid(_ amount: String) -> Bool {
let parts = amount.components(separatedBy: ".")
if parts.count == 1 {
return true
} else if parts.count == 2 {
return parts[1].count <= 8
} else {
return false
}
}

}
6 changes: 6 additions & 0 deletions MixinServices/MixinServices/Services/API/AccountAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ public final class AccountAPI: MixinAPI {
request(method: .get, path: Path.me, completion: completion)
}

public static func me() async throws -> Account {
try await withCheckedThrowingContinuation { continuation in
me(completion: continuation.resume(with:))
}
}

@discardableResult
public static func sendCode(to phoneNumber: String, captchaToken: CaptchaToken?, purpose: VerificationPurpose, completion: @escaping (MixinAPI.Result<VerificationResponse>) -> Void) -> Request? {
var param = ["phone": phoneNumber,
Expand Down
Loading

0 comments on commit 1092074

Please sign in to comment.