From 5500a531b934afdcdcab7dc53147b057afe5d5df Mon Sep 17 00:00:00 2001 From: Nora Trapp Date: Wed, 1 Apr 2020 20:18:13 -0700 Subject: [PATCH] Use PIN progress animation for verification --- .../OWSPinSetupViewController.swift | 4 +- .../Onboarding2FAViewController.swift | 47 ++++++++++++-- .../Registration/OnboardingController.swift | 62 +++++++++---------- .../translations/en.lproj/Localizable.strings | 7 ++- 4 files changed, 77 insertions(+), 43 deletions(-) diff --git a/Signal/src/ViewControllers/OWSPinSetupViewController.swift b/Signal/src/ViewControllers/OWSPinSetupViewController.swift index bd71e72c7a..7c10f346d1 100644 --- a/Signal/src/ViewControllers/OWSPinSetupViewController.swift +++ b/Signal/src/ViewControllers/OWSPinSetupViewController.swift @@ -429,7 +429,7 @@ public class PinSetupViewController: OWSViewController { pinTextField.resignFirstResponder() - let progressView = ProgressView( + let progressView = PinProgressView( loadingText: NSLocalizedString("PIN_CREATION_PIN_PROGRESS", comment: "Indicates the work we are doing while creating the user's pin") ) @@ -520,7 +520,7 @@ extension PinSetupViewController: UITextFieldDelegate { } } -private class ProgressView: UIView { +class PinProgressView: UIView { private let label = UILabel() private let progressAnimation = AnimationView(name: "pinCreationInProgress") private let errorAnimation = AnimationView(name: "pinCreationFail") diff --git a/Signal/src/ViewControllers/Registration/Onboarding2FAViewController.swift b/Signal/src/ViewControllers/Registration/Onboarding2FAViewController.swift index 97b3e33029..2f012838a5 100644 --- a/Signal/src/ViewControllers/Registration/Onboarding2FAViewController.swift +++ b/Signal/src/ViewControllers/Registration/Onboarding2FAViewController.swift @@ -13,6 +13,8 @@ public class Onboarding2FAViewController: OnboardingBaseViewController { private let pinTextField = UITextField() private let pinTypeToggle = UIButton() + private lazy var nextButton = self.primaryButton(title: CommonStrings.nextButton, + selector: #selector(nextPressed)) private lazy var pinStrokeNormal = pinTextField.addBottomStroke() private lazy var pinStrokeError = pinTextField.addBottomStroke(color: .ows_accentRed, strokeWidth: 2) @@ -121,8 +123,6 @@ public class Onboarding2FAViewController: OnboardingBaseViewController { pinTypeToggle.addTarget(self, action: #selector(togglePinType), for: .touchUpInside) pinTypeToggle.accessibilityIdentifier = "pinCreation.pinTypeToggle" - let nextButton = self.primaryButton(title: CommonStrings.nextButton, - selector: #selector(nextPressed)) nextButton.accessibilityIdentifier = "onboarding.2fa." + "nextButton" let primaryButtonView = OnboardingBaseViewController.horizontallyWrap(primaryButton: nextButton) @@ -241,6 +241,30 @@ public class Onboarding2FAViewController: OnboardingBaseViewController { return } + let progressView = PinProgressView( + loadingText: NSLocalizedString("REGISTER_2FA_PIN_PROGRESS", + comment: "Indicates the work we are doing while verifying the user's pin") + ) + view.addSubview(progressView) + progressView.autoPinWidthToSuperview() + progressView.autoVCenterInSuperview() + + progressView.startLoading { + self.view.isUserInteractionEnabled = false + self.nextButton.alpha = 0.5 + self.pinTypeToggle.alpha = 0.5 + } + + func animateProgressFail() { + progressView.loadingComplete(success: false, animateAlongside: { + self.nextButton.alpha = 1 + self.pinTypeToggle.alpha = 1 + }) { + self.view.isUserInteractionEnabled = true + progressView.removeFromSuperview() + } + } + // v1 pins also have a max length, but we'll rely on the server to verify that // since we do not know if this is a v1 or a v2 pin at registration time. @@ -259,17 +283,30 @@ public class Onboarding2FAViewController: OnboardingBaseViewController { } self.attemptState = .invalid(remainingAttempts: nil) + animateProgressFail() case .invalidV2RegistrationLockPin(let remainingAttempts): self.attemptState = .invalid(remainingAttempts: remainingAttempts) + animateProgressFail() case .exhaustedV2RegistrationLockAttempts: self.attemptState = .exhausted - self.showAttemptsExhausted() + + progressView.loadingComplete(success: false, animated: false) { [weak self] in + guard let self = self else { return } + self.showAttemptsExhausted() + } case .success: self.attemptState = .valid - // If we have success while pending restoration, show the next onboarding milestone. - if self.hasPendingRestoration { self.showNextMilestone(wasSuccessful: true) } + + // The completion handler always dismisses this view, so we don't want to animate anything. + progressView.loadingComplete(success: true, animated: false) { [weak self] in + guard let self = self else { return } + // If we have success while pending restoration, show the next onboarding milestone. + if self.hasPendingRestoration { self.showNextMilestone(wasSuccessful: true) } + } + case .invalidVerificationCode: owsFailDebug("Invalid verification code in 2FA view.") + animateProgressFail() } }) } diff --git a/Signal/src/ViewControllers/Registration/OnboardingController.swift b/Signal/src/ViewControllers/Registration/OnboardingController.swift index 9838168b65..ec4f268a7a 100644 --- a/Signal/src/ViewControllers/Registration/OnboardingController.swift +++ b/Signal/src/ViewControllers/Registration/OnboardingController.swift @@ -743,42 +743,36 @@ public class OnboardingController: NSObject { return completion(.invalid2FAPin) } - ModalActivityIndicatorViewController.present(fromViewController: fromViewController, canCancel: false) { modal in - KeyBackupService.restoreKeys(with: twoFAPin, and: self.kbsAuth).done { - // If we restored successfully clear out KBS auth, the server will give it - // to us again if we still need to do KBS operations. - self.kbsAuth = nil + KeyBackupService.restoreKeys(with: twoFAPin, and: self.kbsAuth).done { + // If we restored successfully clear out KBS auth, the server will give it + // to us again if we still need to do KBS operations. + self.kbsAuth = nil - modal.dismiss { - if self.tsAccountManager.isRegistered { - completion(.success) - } else { - // We've restored our keys, we can now re-run this method to post our registration token - self.submitVerification(fromViewController: fromViewController, completion: completion) - } - } - }.catch { error in - modal.dismiss { - guard let error = error as? KeyBackupService.KBSError else { - owsFailDebug("unexpected response from KBS") - return completion(.invalid2FAPin) - } + if self.tsAccountManager.isRegistered { + completion(.success) + } else { + // We've restored our keys, we can now re-run this method to post our registration token + self.submitVerification(fromViewController: fromViewController, completion: completion) + } + }.catch { error in + guard let error = error as? KeyBackupService.KBSError else { + owsFailDebug("unexpected response from KBS") + return completion(.invalid2FAPin) + } - switch error { - case .assertion: - owsFailDebug("unexpected response from KBS") - completion(.invalid2FAPin) - case .invalidPin(let remainingAttempts): - completion(.invalidV2RegistrationLockPin(remainingAttempts: remainingAttempts)) - case .backupMissing: - // We don't have a backup for this person, it probably - // was deleted due to too many failed attempts. They'll - // have to retry after the registration lock window expires. - completion(.exhaustedV2RegistrationLockAttempts) - } - } - }.retainUntilComplete() - } + switch error { + case .assertion: + owsFailDebug("unexpected response from KBS") + completion(.invalid2FAPin) + case .invalidPin(let remainingAttempts): + completion(.invalidV2RegistrationLockPin(remainingAttempts: remainingAttempts)) + case .backupMissing: + // We don't have a backup for this person, it probably + // was deleted due to too many failed attempts. They'll + // have to retry after the registration lock window expires. + completion(.exhaustedV2RegistrationLockAttempts) + } + }.retainUntilComplete() return } diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 1c79ff6249..1b094dcb31 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2391,7 +2391,7 @@ "PIN_CREATION_PIN_CONFIRMATION_HINT" = "Re-enter PIN"; /* Indicates the work we are doing while creating the user's pin */ -"PIN_CREATION_PIN_PROGRESS" = "Creating PIN..."; +"PIN_CREATION_PIN_PROGRESS" = "Creating PIN…"; /* The re-creation explanation in the 'pin creation' view. */ "PIN_CREATION_RECREATION_EXPLANATION" = "You can choose a new PIN because this device is registered. If you forget your PIN, you may need to wait 7 days to register again."; @@ -2711,6 +2711,9 @@ /* Alert title explaining what happens if you forget your 'two-factor auth pin'. */ "REGISTER_2FA_INVALID_PIN_ALERT_TITLE" = "Incorrect PIN"; +/* Indicates the work we are doing while verifying the user's pin */ +"REGISTER_2FA_PIN_PROGRESS" = "Verifying PIN…"; + /* Label for 'submit' button in the 2FA registration view. */ "REGISTER_2FA_SUBMIT_BUTTON" = "Submit"; @@ -2898,7 +2901,7 @@ "SECONDARY_ONBOARDING_GET_STARTED_BY_OPENING_PRIMARY" = "Launch Signal on your phone to link this iPad to your account"; /* Link explaining what to do when trying to link a device before having a primary device. */ -"SECONDARY_ONBOARDING_GET_STARTED_DO_NOT_HAVE_PRIMARY" = "I don't have Signal on my phone..."; +"SECONDARY_ONBOARDING_GET_STARTED_DO_NOT_HAVE_PRIMARY" = "I don't have Signal on my phone…"; /* alert body */ "SECONDARY_ONBOARDING_INSTALL_PRIMARY_FIRST_BODY" = "Go to the App Store on your phone, install Signal, complete the registration process, and then you can link your iPad to the same account.";