import Foundation import UIKit import Display import AsyncDisplayKit import SwiftSignalKit import TelegramCore import TelegramPresentationData import ProgressNavigationButtonNode public final class AuthorizationSequenceCodeEntryController: ViewController { private var controllerNode: AuthorizationSequenceCodeEntryControllerNode { return self.displayNode as! AuthorizationSequenceCodeEntryControllerNode } private var validLayout: ContainerViewLayout? private let strings: PresentationStrings private let theme: PresentationTheme public var loginWithCode: ((String) -> Void)? public var signInWithApple: (() -> Void)? public var openFragment: ((String) -> Void)? var reset: (() -> Void)? var requestNextOption: (() -> Void)? var resetEmail: (() -> Void)? var data: (String, String?, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?)? var termsOfService: (UnauthorizedAccountTermsOfService, Bool)? private let hapticFeedback = HapticFeedback() private var appleSignInAllowed = false public var inProgress: Bool = false { didSet { self.updateNavigationItems() self.controllerNode.inProgress = self.inProgress } } public init(presentationData: PresentationData, back: @escaping () -> Void) { self.strings = presentationData.strings self.theme = presentationData.theme super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(theme), strings: NavigationBarStrings(presentationStrings: strings))) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) self.hasActiveInput = true self.statusBar.statusBarStyle = theme.intro.statusBarStyle.style self.attemptNavigation = { _ in return false } self.navigationBar?.backPressed = { [weak self] in let text: String let proceed: String let stop: String if let (_, _, type, _, _) = self?.data, case .email = type { text = presentationData.strings.Login_CancelEmailVerification proceed = presentationData.strings.Login_CancelEmailVerificationContinue stop = presentationData.strings.Login_CancelEmailVerificationStop } else { text = presentationData.strings.Login_CancelPhoneVerification proceed = presentationData.strings.Login_CancelPhoneVerificationContinue stop = presentationData.strings.Login_CancelPhoneVerificationStop } self?.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .genericAction, title: proceed, action: { }), TextAlertAction(type: .defaultAction, title: stop, action: { back() })]), in: .window(.root)) } } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } override public func loadDisplayNode() { self.displayNode = AuthorizationSequenceCodeEntryControllerNode(strings: self.strings, theme: self.theme) self.displayNodeDidLoad() self.controllerNode.view.disableAutomaticKeyboardHandling = [.forward, .backward] self.controllerNode.loginWithCode = { [weak self] code in self?.continueWithCode(code) } self.controllerNode.signInWithApple = { [weak self] in self?.signInWithApple?() } self.controllerNode.openFragment = { [weak self] url in self?.openFragment?(url) } self.controllerNode.requestNextOption = { [weak self] in self?.requestNextOption?() } self.controllerNode.requestAnotherOption = { [weak self] in self?.requestNextOption?() } self.controllerNode.updateNextEnabled = { [weak self] value in self?.navigationItem.rightBarButtonItem?.isEnabled = value } self.controllerNode.reset = { [weak self] in self?.resetEmail?() } self.controllerNode.present = { [weak self] c, a in self?.present(c, in: .window(.root), with: a) } if let (number, email, codeType, nextType, timeout) = self.data { var appleSignInAllowed = false if case let .email(_, _, _, _, appleSignInAllowedValue, _) = codeType { appleSignInAllowed = appleSignInAllowedValue } self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed) } } override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if let navigationController = self.navigationController as? NavigationController, let layout = self.validLayout { addTemporaryKeyboardSnapshotView(navigationController: navigationController, parentView: self.view, layout: layout) } self.controllerNode.activateInput() } public func resetCode() { self.controllerNode.resetCode() } public func animateSuccess() { self.controllerNode.animateSuccess() } public func animateError(text: String) { self.hapticFeedback.error() self.controllerNode.animateError(text: text) } func updateNavigationItems() { guard let layout = self.validLayout, layout.size.width < 360.0 else { return } if self.inProgress { let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.theme.rootController.navigationBar.accentTextColor)) self.navigationItem.rightBarButtonItem = item } else { self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed)) } } public func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) { self.termsOfService = termsOfService if self.data?.0 != number || self.data?.1 != email || self.data?.2 != codeType || self.data?.3 != nextType || self.data?.4 != timeout { self.data = (number, email, codeType, nextType, timeout) var appleSignInAllowed = false if case let .email(_, _, _, _, appleSignInAllowedValue, _) = codeType { appleSignInAllowed = appleSignInAllowedValue } if self.isNodeLoaded { self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed) self.requestLayout(transition: .immediate) } } } override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) let hadLayout = self.validLayout != nil self.validLayout = layout if !hadLayout { self.updateNavigationItems() } self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) } @objc private func nextPressed() { guard let (_, _, type, _, _) = self.data else { return } var minimalCodeLength = 1 switch type { case let .otherSession(length): minimalCodeLength = Int(length) case let .sms(length): minimalCodeLength = Int(length) case let .call(length): minimalCodeLength = Int(length) case let .missedCall(_, length): minimalCodeLength = Int(length) case let .email(_, length, _, _, _, _): minimalCodeLength = Int(length) case let .fragment(_, length): minimalCodeLength = Int(length) case let .firebase(_, length): minimalCodeLength = Int(length) case .flashCall, .emailSetupRequired: break } if self.controllerNode.currentCode.count < minimalCodeLength { self.hapticFeedback.error() self.controllerNode.animateError() } else { self.continueWithCode(self.controllerNode.currentCode) } } private func continueWithCode(_ code: String) { self.loginWithCode?(code) } public func applyConfirmationCode(_ code: Int) { self.controllerNode.updateCode("\(code)") } } func addTemporaryKeyboardSnapshotView(navigationController: NavigationController, parentView: UIView, layout: ContainerViewLayout) { if case .compact = layout.metrics.widthClass, let statusBarHost = navigationController.statusBarHost { if let keyboardView = statusBarHost.keyboardView { if let snapshotView = keyboardView.snapshotView(afterScreenUpdates: false) { keyboardView.layer.removeAllAnimations() UIView.performWithoutAnimation { snapshotView.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - snapshotView.frame.size.height), size: snapshotView.frame.size) if let keyboardWindow = statusBarHost.keyboardWindow { keyboardWindow.addSubview(snapshotView) } Queue.mainQueue().after(0.5, { snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() }) }) } } } } }