Swiftgram/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift
2019-11-02 23:01:31 +04:00

710 lines
42 KiB
Swift

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 context: AccountContext
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(context: AccountContext, updateBackAction: @escaping (Bool) -> Void, updateNextAction: @escaping (SetupTwoStepVerificationNextAction) -> Void, stateUpdated: @escaping (SetupTwoStepVerificationStateUpdate, Bool) -> Void, present: @escaping (ViewController, Any?) -> Void, dismiss: @escaping () -> Void, initialState: SetupTwoStepVerificationInitialState) {
self.context = context
self.updateBackAction = updateBackAction
self.updateNextAction = updateNextAction
self.stateUpdated = stateUpdated
self.present = present
self.dismiss = dismiss
self.presentationData = context.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(context.account.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.context.account.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(context: strongSelf.context, 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.context.account.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(context: strongSelf.context, 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(context: strongSelf.context, 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.context.account.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(context: strongSelf.context, 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.context.account.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(context: strongSelf.context, 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.context.account.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(context: strongSelf.context, 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.context.account.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(context: strongSelf.context, 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(enterEmail)? = self.innerState.data.state, case .create = enterEmail.state, enterEmail.email.isEmpty {
self.present(textAlertController(context: self.context, 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()
}
}
}