mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
713 lines
42 KiB
Swift
713 lines
42 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import Postbox
|
|
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)
|
|
}
|
|
|
|
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 {
|
|
if inputHeight.isEqual(to: state.layout.layout.standardInputHeight - 44.0) {
|
|
insets.bottom += state.layout.layout.standardInputHeight
|
|
} else {
|
|
insets.bottom += inputHeight
|
|
}
|
|
}
|
|
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()
|
|
}
|
|
}
|
|
}
|