Swiftgram/submodules/PasswordSetupUI/Sources/SetupTwoStepVerificationControllerNode.swift
2023-04-19 23:47:38 +04:00

708 lines
42 KiB
Swift

import Foundation
import UIKit
import Display
import AsyncDisplayKit
import TelegramCore
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)
case pendingPasswordReset
}
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 = self.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((self.context.engine.auth.twoStepAuthData()
|> 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((strongSelf.context.engine.auth.updateTwoStepVerificationPassword(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.context.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).string
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((strongSelf.context.engine.auth.resendTwoStepRecoveryEmail()
|> 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.context.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(_, _, codeLength?, code)? = strongSelf.innerState.data.state, 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.context.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((strongSelf.context.engine.auth.updateTwoStepVerificationPassword(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.context.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((strongSelf.context.engine.auth.updateTwoStepVerificationPassword(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.context.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((strongSelf.context.engine.auth.updateTwoStepVerificationEmail(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.context.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((strongSelf.context.engine.auth.confirmTwoStepRecoveryEmail(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.context.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.context.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()
}
}
}