import Foundation import UIKit import Display import AsyncDisplayKit import Postbox import TelegramCore import SyncCore import SwiftSignalKit import TelegramPresentationData import ActivityIndicator import AccountContext import AlertUI import PresentationDataUtils public enum SetupTwoStepVerificationInitialState { case automatic case createPassword case updatePassword(current: String, hasRecoveryEmail: Bool, hasSecureValues: Bool) case addEmail(hadRecoveryEmail: Bool, hasSecureValues: Bool, password: String) case confirmEmail(password: String?, hasSecureValues: Bool, pattern: String, codeLength: Int32?) } enum SetupTwoStepVerificationStateKind: Int32 { case enterPassword case confirmPassword case enterHint case enterEmail case confirmEmail } private enum CreatePasswordMode: Equatable { case create case update(current: String, hasRecoveryEmail: Bool, hasSecureValues: Bool) } private enum EnterEmailState: Equatable { case create(password: String, hint: String) case add(hadRecoveryEmail: Bool, hasSecureValues: Bool, password: String) } private enum ConfirmEmailState: Equatable { case create(password: String, hint: String, email: String) case add(password: String, hadRecoveryEmail: Bool, hasSecureValues: Bool, email: String) case confirm(password: String?, hasSecureValues: Bool, pattern: String, codeLength: Int32?) } private enum SetupTwoStepVerificationState: Equatable { case enterPassword(mode: CreatePasswordMode, password: String) case confirmPassword(mode: CreatePasswordMode, password: String, confirmation: String) case enterHint(mode: CreatePasswordMode, password: String, hint: String) case enterEmail(state: EnterEmailState, email: String) case confirmEmail(state: ConfirmEmailState, pattern: String, codeLength: Int32?, code: String) var kind: SetupTwoStepVerificationStateKind { switch self { case .enterPassword: return .enterPassword case .confirmPassword: return .confirmPassword case .enterHint: return .enterHint case .enterEmail: return .enterEmail case .confirmEmail: return .confirmEmail } } mutating func updateInputText(_ text: String) { switch self { case let .enterPassword(mode, _): self = .enterPassword(mode: mode, password: text) case let .confirmPassword(mode, password, _): self = .confirmPassword(mode: mode, password: password, confirmation: text) case let .enterHint(mode, password, _): self = .enterHint(mode: mode, password: password, hint: text) case let .enterEmail(state, _): self = .enterEmail(state: state, email: text) case let .confirmEmail(state, pattern, codeLength, _): self = .confirmEmail(state: state, pattern: pattern, codeLength: codeLength, code: text) } } } extension SetupTwoStepVerificationState { init?(initialState: SetupTwoStepVerificationInitialState) { switch initialState { case .automatic: return nil case .createPassword: self = .enterPassword(mode: .create, password: "") case let .updatePassword(current, hasRecoveryEmail, hasSecureValues): self = .enterPassword(mode: .update(current: current, hasRecoveryEmail: hasRecoveryEmail, hasSecureValues: hasSecureValues), password: "") case let .addEmail(hadRecoveryEmail, hasSecureValues, password): self = .enterEmail(state: .add(hadRecoveryEmail: hadRecoveryEmail, hasSecureValues: hasSecureValues, password: password), email: "") case let .confirmEmail(password, hasSecureValues, pattern, codeLength): self = .confirmEmail(state: .confirm(password: password, hasSecureValues: hasSecureValues, pattern: pattern, codeLength: codeLength), pattern: pattern, codeLength: codeLength, code: "") } } } private struct SetupTwoStepVerificationControllerDataState: Equatable { var activity: Bool var state: SetupTwoStepVerificationState? } private struct SetupTwoStepVerificationControllerLayoutState: Equatable { let layout: ContainerViewLayout let navigationHeight: CGFloat } private struct SetupTwoStepVerificationControllerInnerState: Equatable { var layout: SetupTwoStepVerificationControllerLayoutState? var data: SetupTwoStepVerificationControllerDataState } private struct SetupTwoStepVerificationControllerState: Equatable { var layout: SetupTwoStepVerificationControllerLayoutState var data: SetupTwoStepVerificationControllerDataState } extension SetupTwoStepVerificationControllerState { init?(_ state: SetupTwoStepVerificationControllerInnerState) { guard let layout = state.layout else { return nil } self.init(layout: layout, data: state.data) } } enum SetupTwoStepVerificationNextAction: Equatable { case none case activity case button(title: String, isEnabled: Bool) } public enum SetupTwoStepVerificationStateUpdate { case noPassword case awaitingEmailConfirmation(password: String, pattern: String, codeLength: Int32?) case passwordSet(password: String?, hasRecoveryEmail: Bool, hasSecureValues: Bool) } final class SetupTwoStepVerificationControllerNode: ViewControllerTracingNode { private let sharedContext: SharedAccountContext private let network: Network private var presentationData: PresentationData private let updateBackAction: (Bool) -> Void private let updateNextAction: (SetupTwoStepVerificationNextAction) -> Void private let stateUpdated: (SetupTwoStepVerificationStateUpdate, Bool) -> Void private let present: (ViewController, Any?) -> Void private let dismiss: () -> Void private var innerState: SetupTwoStepVerificationControllerInnerState private let activityIndicator: ActivityIndicator private var contentNode: SetupTwoStepVerificationContentNode? private let actionDisposable = MetaDisposable() init(sharedContext: SharedAccountContext, network: Network, updateBackAction: @escaping (Bool) -> Void, updateNextAction: @escaping (SetupTwoStepVerificationNextAction) -> Void, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate, Bool) -> Void, present: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void, initialState: SetupTwoStepVerificationInitialState) { self.sharedContext = sharedContext self.network = network self.updateBackAction = updateBackAction self.updateNextAction = updateNextAction self.stateUpdated = stateUpdated self.present = present self.dismiss = dismiss self.presentationData = self.sharedContext.currentPresentationData.with { $0 } self.innerState = SetupTwoStepVerificationControllerInnerState(layout: nil, data: SetupTwoStepVerificationControllerDataState(activity: false, state: SetupTwoStepVerificationState(initialState: initialState))) self.activityIndicator = ActivityIndicator(type: .custom(self.presentationData.theme.list.itemAccentColor, 22.0, 2.0, false)) super.init() self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.processStateUpdated() if self.innerState.data.state == nil { self.actionDisposable.set((twoStepAuthData(self.network) |> deliverOnMainQueue).start(next: { [weak self] data in guard let strongSelf = self else { return } if data.currentPasswordDerivation != nil { strongSelf.stateUpdated(.passwordSet(password: nil, hasRecoveryEmail: data.hasRecovery, hasSecureValues: data.hasSecretValues), true) } else { strongSelf.updateState({ state in var state = state if let unconfirmedEmailPattern = data.unconfirmedEmailPattern { state.data.state = .confirmEmail(state: .confirm(password: nil, hasSecureValues: data.hasSecretValues, pattern: unconfirmedEmailPattern, codeLength: nil), pattern: unconfirmedEmailPattern, codeLength: nil, code: "") } else { state.data.state = .enterPassword(mode: .create, password: "") } return state }, transition: .animated(duration: 0.3, curve: .easeInOut)) } })) } } deinit { self.actionDisposable.dispose() } func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.contentNode?.updatePresentationData(presentationData) } func animateIn(completion: (() -> Void)? = nil) { self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in completion?() }) } func animateOut(completion: (() -> Void)? = nil) { self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { _ in completion?() }) } private func updateState(_ f: (SetupTwoStepVerificationControllerInnerState) -> SetupTwoStepVerificationControllerInnerState, transition: ContainedViewLayoutTransition) { let updatedState = f(self.innerState) if updatedState != self.innerState { self.innerState = updatedState self.processStateUpdated() if let state = SetupTwoStepVerificationControllerState(updatedState) { self.transition(state: state, transition: transition) } } } private func processStateUpdated() { var backAction = false let nextAction: SetupTwoStepVerificationNextAction if self.innerState.data.activity { nextAction = .activity } else if let state = self.innerState.data.state { switch state { case let .enterPassword(_, password): nextAction = .button(title: self.presentationData.strings.Common_Next, isEnabled: !password.isEmpty) case let .confirmPassword(_, _, confirmation): nextAction = .button(title: self.presentationData.strings.Common_Next, isEnabled: !confirmation.isEmpty) backAction = true case let .enterHint(_, _, hint): nextAction = .button(title: hint.isEmpty ? self.presentationData.strings.TwoStepAuth_EmailSkip : self.presentationData.strings.Common_Next, isEnabled: true) backAction = true case let .enterEmail(enterState, email): switch enterState { case .create: nextAction = .button(title: email.isEmpty ? self.presentationData.strings.TwoStepAuth_EmailSkip : self.presentationData.strings.Common_Next, isEnabled: true) case .add: nextAction = .button(title: self.presentationData.strings.Common_Next, isEnabled: !email.isEmpty) } case let .confirmEmail(_, _, _, code): nextAction = .button(title: self.presentationData.strings.Common_Next, isEnabled: !code.isEmpty) } } else { nextAction = .none } self.updateBackAction(backAction) self.updateNextAction(nextAction) } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.updateState({ state in var state = state state.layout = SetupTwoStepVerificationControllerLayoutState(layout: layout, navigationHeight: navigationBarHeight) return state }, transition: transition) let indicatorSize = CGSize(width: 22.0, height: 22.0) self.activityIndicator.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - indicatorSize.width) / 2.0), y: floor((layout.size.height - indicatorSize.height) / 2.0)), size: indicatorSize) } private func transition(state: SetupTwoStepVerificationControllerState, transition: ContainedViewLayoutTransition) { var insets = state.layout.layout.insets(options: [.statusBar]) let visibleInsets = state.layout.layout.insets(options: [.statusBar, .input]) if let inputHeight = state.layout.layout.inputHeight { insets.bottom += max(inputHeight, state.layout.layout.standardInputHeight) } let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: state.layout.layout.size.width, height: state.layout.layout.size.height)) if state.data.state?.kind != self.contentNode?.kind { if let dataState = state.data.state { let title: String let subtitle: String let inputType: SetupTwoStepVerificationInputType let inputPlaceholder: String let inputText: String let isPassword: Bool var leftAction: SetupTwoStepVerificationContentAction? var rightAction: SetupTwoStepVerificationContentAction? switch dataState { case let .enterPassword(mode, password): switch mode { case .create: title = self.presentationData.strings.TwoStepAuth_SetupPasswordTitle subtitle = self.presentationData.strings.TwoStepAuth_SetupPasswordDescription case .update: title = self.presentationData.strings.TwoStepAuth_ChangePassword subtitle = self.presentationData.strings.TwoStepAuth_ChangePasswordDescription } inputType = .password inputPlaceholder = self.presentationData.strings.LoginPassword_PasswordPlaceholder inputText = password isPassword = true case let .confirmPassword(_, _, confirmation): title = self.presentationData.strings.TwoStepAuth_ReEnterPasswordTitle subtitle = self.presentationData.strings.TwoStepAuth_ReEnterPasswordDescription inputType = .password inputPlaceholder = self.presentationData.strings.LoginPassword_PasswordPlaceholder inputText = confirmation isPassword = true case let .enterHint(_, _, hint): title = self.presentationData.strings.TwoStepAuth_AddHintTitle subtitle = self.presentationData.strings.TwoStepAuth_AddHintDescription inputType = .text inputPlaceholder = self.presentationData.strings.TwoStepAuth_HintPlaceholder inputText = hint isPassword = false case let .enterEmail(enterState, email): title = self.presentationData.strings.TwoStepAuth_RecoveryEmailTitle switch enterState { case let .add(hadRecoveryEmail, _, _) where hadRecoveryEmail: subtitle = self.presentationData.strings.TwoStepAuth_RecoveryEmailChangeDescription default: subtitle = self.presentationData.strings.TwoStepAuth_RecoveryEmailAddDescription } inputType = .email inputPlaceholder = self.presentationData.strings.TwoStepAuth_EmailPlaceholder inputText = email isPassword = false case let .confirmEmail(confirmState, _, _, code): title = self.presentationData.strings.TwoStepAuth_RecoveryEmailTitle let emailPattern: String switch confirmState { case let .create(password, hint, email): emailPattern = email leftAction = SetupTwoStepVerificationContentAction(title: self.presentationData.strings.TwoStepAuth_ChangeEmail, action: { [weak self] in guard let strongSelf = self else { return } strongSelf.updateState({ state in var state = state state.data.activity = true return state }, transition: .animated(duration: 0.5, curve: .spring)) strongSelf.actionDisposable.set((updateTwoStepVerificationPassword(network: strongSelf.network, currentPassword: nil, updatedPassword: .none) |> deliverOnMainQueue).start(next: { _ in guard let strongSelf = self else { return } strongSelf.updateState({ state in var state = state state.data.activity = false state.data.state = .enterEmail(state: .create(password: password, hint: hint), email: "") return state }, transition: .animated(duration: 0.5, curve: .spring)) strongSelf.stateUpdated(.noPassword, false) }, error: { _ in guard let strongSelf = self else { return } strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) strongSelf.updateState({ state in var state = state state.data.activity = false return state }, transition: .animated(duration: 0.5, curve: .spring)) })) }) case let .add(password, hadRecoveryEmail, hasSecureValues, email): emailPattern = email leftAction = SetupTwoStepVerificationContentAction(title: self.presentationData.strings.TwoStepAuth_ChangeEmail, action: { [weak self] in guard let strongSelf = self else { return } strongSelf.updateState({ state in var state = state state.data.state = .enterEmail(state: .add(hadRecoveryEmail: hadRecoveryEmail, hasSecureValues: hasSecureValues, password: password), email: "") return state }, transition: .animated(duration: 0.5, curve: .spring)) }) case let .confirm(_, _, pattern, _): emailPattern = pattern } subtitle = self.presentationData.strings.TwoStepAuth_ConfirmEmailDescription(emailPattern).0 inputType = .code inputPlaceholder = self.presentationData.strings.TwoStepAuth_ConfirmEmailCodePlaceholder inputText = code isPassword = true rightAction = SetupTwoStepVerificationContentAction(title: self.presentationData.strings.TwoStepAuth_ConfirmEmailResendCode, action: { [weak self] in guard let strongSelf = self else { return } strongSelf.updateState({ state in var state = state state.data.activity = true return state }, transition: .animated(duration: 0.5, curve: .spring)) strongSelf.actionDisposable.set((resendTwoStepRecoveryEmail(network: strongSelf.network) |> deliverOnMainQueue).start(error: { error in guard let strongSelf = self else { return } let text: String switch error { case .flood: text = strongSelf.presentationData.strings.TwoStepAuth_FloodError case .generic: text = strongSelf.presentationData.strings.Login_UnknownError } strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) strongSelf.updateState({ state in var state = state state.data.activity = false return state }, transition: .animated(duration: 0.5, curve: .spring)) }, completed: { guard let strongSelf = self else { return } strongSelf.updateState({ state in var state = state state.data.activity = false return state }, transition: .animated(duration: 0.5, curve: .spring)) })) }) } let contentNode = SetupTwoStepVerificationContentNode(theme: self.presentationData.theme, kind: dataState.kind, title: title, subtitle: subtitle, inputType: inputType, placeholder: inputPlaceholder, text: inputText, isPassword: isPassword, textUpdated: { [weak self] text in guard let strongSelf = self else { return } var inplicitelyActivateNextAction = false if case let .confirmEmail(confirmEmail)? = strongSelf.innerState.data.state, let codeLength = confirmEmail.codeLength, confirmEmail.code.count != codeLength, text.count == codeLength { inplicitelyActivateNextAction = true } strongSelf.updateState({ state in var state = state state.data.state?.updateInputText(text) return state }, transition: .immediate) if inplicitelyActivateNextAction { strongSelf.activateNextAction() } }, returnPressed: { [weak self] in self?.activateNextAction() }, leftAction: leftAction, rightAction: rightAction) self.insertSubnode(contentNode, at: 0) contentNode.updateIsEnabled(!state.data.activity) contentNode.updateLayout(size: contentFrame.size, insets: insets, visibleInsets: visibleInsets, transition: .immediate) contentNode.frame = contentFrame contentNode.activate() if let currentContentNode = self.contentNode { if currentContentNode.kind.rawValue < contentNode.kind.rawValue { transition.updatePosition(node: currentContentNode, position: CGPoint(x: -contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in currentContentNode?.removeFromSupernode() }) transition.animateHorizontalOffsetAdditive(node: contentNode, offset: -contentFrame.width) } else { transition.updatePosition(node: currentContentNode, position: CGPoint(x: contentFrame.size.width + contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in currentContentNode?.removeFromSupernode() }) transition.animateHorizontalOffsetAdditive(node: contentNode, offset: contentFrame.width) } } else if transition.isAnimated { contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } if self.activityIndicator.supernode != nil { transition.updateAlpha(node: self.activityIndicator, alpha: 0.0, completion: { [weak self] _ in self?.activityIndicator.removeFromSupernode() }) } self.contentNode = contentNode } else if let currentContentNode = self.contentNode { transition.updateAlpha(node: currentContentNode, alpha: 0.0, completion: { [weak currentContentNode] _ in currentContentNode?.removeFromSupernode() }) if self.activityIndicator.supernode == nil { self.addSubnode(self.activityIndicator) transition.updateAlpha(node: self.activityIndicator, alpha: 1.0) } self.contentNode = nil } } else if let contentNode = self.contentNode { contentNode.updateIsEnabled(!state.data.activity) transition.updateFrame(node: contentNode, frame: contentFrame) contentNode.updateLayout(size: contentFrame.size, insets: insets, visibleInsets: visibleInsets, transition: transition) } } func activateBackAction() { if self.innerState.data.activity { return } self.updateState({ state in var state = state if let dataState = state.data.state { switch dataState { case let .confirmPassword(mode, _, _): state.data.state = .enterPassword(mode: mode, password: "") case let .enterHint(mode, _, _): state.data.state = .enterPassword(mode: mode, password: "") default: break } } return state }, transition: .animated(duration: 0.5, curve: .spring)) } func activateNextAction() { if self.innerState.data.activity { return } let continueImpl: () -> Void = { [weak self] in guard let strongSelf = self else { return } strongSelf.updateState({ state in guard let dataState = state.data.state else { return state } var state = state switch dataState { case let .enterPassword(mode, password): state.data.state = .confirmPassword(mode: mode, password: password, confirmation: "") case let .confirmPassword(mode, password, confirmation): if password == confirmation { state.data.state = .enterHint(mode: mode, password: password, hint: "") } else { strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.TwoStepAuth_SetupPasswordConfirmFailed, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) } case let .enterHint(mode, password, hint): switch mode { case .create: state.data.state = .enterEmail(state: .create(password: password, hint: hint), email: "") case let .update(current, hasRecoveryEmail, hasSecureValues): state.data.activity = true strongSelf.actionDisposable.set((updateTwoStepVerificationPassword(network: strongSelf.network, currentPassword: current, updatedPassword: .password(password: password, hint: hint, email: nil)) |> deliverOnMainQueue).start(next: { result in guard let strongSelf = self else { return } strongSelf.updateState({ state in var state = state state.data.activity = false switch result { case let .password(password, pendingEmail): if let pendingEmail = pendingEmail { strongSelf.stateUpdated(.awaitingEmailConfirmation(password: password, pattern: pendingEmail.pattern, codeLength: pendingEmail.codeLength), true) } else { strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: hasRecoveryEmail, hasSecureValues: hasSecureValues), true) } case .none: strongSelf.dismiss() } return state }, transition: .animated(duration: 0.5, curve: .spring)) }, error: { error in guard let strongSelf = self else { return } strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) strongSelf.updateState({ state in var state = state state.data.activity = false return state }, transition: .animated(duration: 0.5, curve: .spring)) })) } case let .enterEmail(enterState, email): state.data.activity = true switch enterState { case let .create(password, hint): strongSelf.actionDisposable.set((updateTwoStepVerificationPassword(network: strongSelf.network, currentPassword: nil, updatedPassword: .password(password: password, hint: hint, email: email)) |> deliverOnMainQueue).start(next: { result in guard let strongSelf = self else { return } strongSelf.updateState({ state in var state = state switch result { case let .password(password, pendingEmail): if let pendingEmail = pendingEmail { state.data.activity = false state.data.state = .confirmEmail(state: .create(password: password, hint: hint, email: email), pattern: pendingEmail.pattern, codeLength: pendingEmail.codeLength, code: "") strongSelf.stateUpdated(.awaitingEmailConfirmation(password: password, pattern: pendingEmail.pattern, codeLength: pendingEmail.codeLength), false) } else { strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: false, hasSecureValues: false), true) } case .none: break } return state }, transition: .animated(duration: 0.5, curve: .spring)) }, error: { error in guard let strongSelf = self else { return } let text: String switch error { case .invalidEmail: text = strongSelf.presentationData.strings.TwoStepAuth_EmailInvalid case .generic: text = strongSelf.presentationData.strings.Login_UnknownError } strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) strongSelf.updateState({ state in var state = state state.data.activity = false if case .invalidEmail = error { state.data.state = .enterEmail(state: .create(password: password, hint: hint), email: "") } return state }, transition: .animated(duration: 0.5, curve: .spring)) })) case let .add(hadRecoveryEmail, hasSecureValues, password): strongSelf.updateState({ state in var state = state state.data.activity = true return state }, transition: .animated(duration: 0.5, curve: .spring)) strongSelf.actionDisposable.set((updateTwoStepVerificationEmail(network: strongSelf.network, currentPassword: password, updatedEmail: email) |> deliverOnMainQueue).start(next: { result in guard let strongSelf = self else { return } strongSelf.updateState({ state in var state = state state.data.activity = false switch result { case .none: assertionFailure() break case let .password(password, pendingEmail): if let pendingEmail = pendingEmail { state.data.state = .confirmEmail(state: .add(password: password, hadRecoveryEmail: hadRecoveryEmail, hasSecureValues: hasSecureValues, email: email), pattern: pendingEmail.pattern, codeLength: pendingEmail.codeLength, code: "") strongSelf.stateUpdated(.awaitingEmailConfirmation(password: password, pattern: pendingEmail.pattern, codeLength: pendingEmail.codeLength), false) } else { strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: true, hasSecureValues: hasSecureValues), true) } } return state }, transition: .animated(duration: 0.5, curve: .spring)) }, error: { _ in guard let strongSelf = self else { return } strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: strongSelf.presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) strongSelf.updateState({ state in var state = state state.data.activity = false return state }, transition: .animated(duration: 0.5, curve: .spring)) })) } case let .confirmEmail(confirmState, _, _, code): state.data.activity = true strongSelf.actionDisposable.set((confirmTwoStepRecoveryEmail(network: strongSelf.network, code: code) |> deliverOnMainQueue).start(error: { error in guard let strongSelf = self else { return } let text: String switch error { case .invalidEmail: text = strongSelf.presentationData.strings.TwoStepAuth_EmailInvalid case .invalidCode: text = strongSelf.presentationData.strings.Login_InvalidCodeError strongSelf.contentNode?.dataEntryError() case .expired: text = strongSelf.presentationData.strings.TwoStepAuth_EmailCodeExpired case .flood: text = strongSelf.presentationData.strings.TwoStepAuth_FloodError case .generic: text = strongSelf.presentationData.strings.Login_UnknownError } strongSelf.present(textAlertController(sharedContext: strongSelf.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), nil) strongSelf.updateState({ state in var state = state state.data.activity = false return state }, transition: .animated(duration: 0.5, curve: .spring)) }, completed: { guard let strongSelf = self else { return } switch confirmState { case let .create(password, _, _): strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: true, hasSecureValues: false), true) case let .add(password, _, hasSecureValues, email): strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: !email.isEmpty, hasSecureValues: hasSecureValues), true) case let .confirm(password, hasSecureValues, _, _): strongSelf.stateUpdated(.passwordSet(password: password, hasRecoveryEmail: true, hasSecureValues: hasSecureValues), true) } })) } return state }, transition: .animated(duration: 0.5, curve: .spring)) } if case let .enterEmail(enterEmailState, enterEmailEmail)? = self.innerState.data.state, case .create = enterEmailState, enterEmailEmail.isEmpty { self.present(textAlertController(sharedContext: self.sharedContext, title: nil, text: self.presentationData.strings.TwoStepAuth_EmailSkipAlert, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .destructiveAction, title: self.presentationData.strings.TwoStepAuth_EmailSkip, action: { continueImpl() })]), nil) } else { continueImpl() } } }