Swiftgram/TelegramUI/SecureIdAuthControllerNode.swift
Peter Iakovlev aa60d76fd8 no message
2018-03-27 12:03:25 +04:00

184 lines
9.9 KiB
Swift

import Foundation
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
final class SecureIdAuthControllerNode: ViewControllerTracingNode {
private let account: Account
private var presentationData: PresentationData
private let requestLayout: (ContainedViewLayoutTransition) -> Void
private let interaction: SecureIdAuthControllerInteraction
private var validLayout: (ContainerViewLayout, CGFloat)?
private let headerNode: SecureIdAuthHeaderNode
private var contentNode: (ASDisplayNode & SecureIdAuthContentNode)?
private var scheduledLayoutTransitionRequestId: Int = 0
private var scheduledLayoutTransitionRequest: (Int, ContainedViewLayoutTransition)?
private var state: SecureIdAuthControllerState?
init(account: Account, presentationData: PresentationData, requestLayout: @escaping (ContainedViewLayoutTransition) -> Void, interaction: SecureIdAuthControllerInteraction) {
self.account = account
self.presentationData = presentationData
self.requestLayout = requestLayout
self.interaction = interaction
self.headerNode = SecureIdAuthHeaderNode(account: account, theme: presentationData.theme, strings: presentationData.strings)
super.init()
self.backgroundColor = presentationData.theme.list.blocksBackgroundColor
}
func animateIn() {
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)
}
func animateOut(completion: (() -> Void)? = nil) {
self.view.endEditing(true)
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: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { _ in
completion?()
})
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight)
var insets = layout.insets(options: [.input])
insets.bottom = max(insets.bottom, layout.safeInsets.bottom)
let contentRect = CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight), size: CGSize(width: layout.size.width, height: layout.size.height - insets.bottom - navigationBarHeight))
let headerNodeTransition: ContainedViewLayoutTransition = headerNode.bounds.isEmpty ? .immediate : transition
let headerHeight = self.headerNode.updateLayout(width: layout.size.width, transition: headerNodeTransition)
if let contentNode = self.contentNode {
let contentFirstTime = contentNode.bounds.isEmpty
let contentNodeTransition: ContainedViewLayoutTransition = contentFirstTime ? .immediate : transition
let contentLayout = contentNode.updateLayout(width: layout.size.width, transition: contentNodeTransition)
let contentSpacing: CGFloat = 70.0
let boundingHeight = headerHeight + contentLayout.height + contentSpacing
let boundingRect = CGRect(origin: CGPoint(x: 0.0, y: contentRect.minY + floor((contentRect.height - boundingHeight) / 2.0)), size: CGSize(width: layout.size.width, height: boundingHeight))
headerNodeTransition.updateFrame(node: self.headerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: boundingRect.minY), size: CGSize(width: boundingRect.width, height: headerHeight)))
contentNodeTransition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: 0.0, y: boundingRect.minY + headerHeight + contentSpacing), size: CGSize(width: boundingRect.width, height: contentLayout.height)))
if contentFirstTime {
contentNode.didAppear()
if transition.isAnimated {
contentNode.animateIn()
if !(contentNode is SecureIdAuthPasswordOptionContentNode) {
transition.animateOffsetAdditive(node: contentNode, offset: -layout.size.height)
}
}
}
}
}
func transitionToContentNode(_ contentNode: (ASDisplayNode & SecureIdAuthContentNode)?, transition: ContainedViewLayoutTransition) {
if let current = self.contentNode {
current.willDisappear()
current.layer.animateBoundsOriginYAdditive(from: 0.0, to: -self.bounds.height, duration: 0.3, removeOnCompletion: false)
current.animateOut { [weak current] in
current?.removeFromSupernode()
}
}
self.contentNode = contentNode
if let contentNode = self.contentNode {
self.addSubnode(contentNode)
if let _ = self.validLayout {
self.scheduleLayoutTransitionRequest(.animated(duration: 0.5, curve: .spring))
}
}
}
func updateState(_ state: SecureIdAuthControllerState, transition: ContainedViewLayoutTransition) {
self.state = state
if let encryptedFormData = state.encryptedFormData, let verificationState = state.verificationState {
if self.headerNode.supernode == nil {
self.addSubnode(self.headerNode)
self.headerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
self.headerNode.updateState(formData: encryptedFormData, verificationState: verificationState)
var contentNode: (ASDisplayNode & SecureIdAuthContentNode)?
switch verificationState {
case let .passwordChallenge(challengeState):
if let current = self.contentNode as? SecureIdAuthPasswordOptionContentNode {
current.updateIsChecking(challengeState == .checking)
contentNode = current
} else {
let current = SecureIdAuthPasswordOptionContentNode(theme: presentationData.theme, strings: presentationData.strings, checkPassword: { [weak self] password in
if let strongSelf = self {
strongSelf.interaction.checkPassword(password)
}
})
current.updateIsChecking(challengeState == .checking)
contentNode = current
}
case .noChallenge:
contentNode = nil
case .verified:
if let formData = state.formData {
if let current = self.contentNode as? SecureIdAuthFormContentNode {
contentNode = current
} else {
let current = SecureIdAuthFormContentNode(theme: self.presentationData.theme, strings: self.presentationData.strings, form: formData, openField: { [weak self] type in
if let strongSelf = self, let state = strongSelf.state, let verificationState = state.verificationState, case let .verified(context) = verificationState, let formData = state.formData {
strongSelf.interaction.present(SecureIdIdentityFormController(account: strongSelf.account, context: context, type: .passport, value: findIdentity(formData.values)?.1, updatedValue: { value in
if let strongSelf = self {
strongSelf.interaction.updateState { state in
if let formData = state.formData {
var values = formData.values
while let index = findIdentity(formData.values)?.0 {
values.remove(at: index)
}
if let value = value {
values.append(.identity(value))
}
return SecureIdAuthControllerState(encryptedFormData: state.encryptedFormData, formData: SecureIdForm(peerId: formData.peerId, requestedFields: formData.requestedFields, values: values), verificationState: state.verificationState)
}
return state
}
}
}), nil)
}
})
contentNode = current
}
}
}
if self.contentNode !== contentNode {
self.transitionToContentNode(contentNode, transition: transition)
}
}
}
private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) {
let requestId = self.scheduledLayoutTransitionRequestId
self.scheduledLayoutTransitionRequestId += 1
self.scheduledLayoutTransitionRequest = (requestId, transition)
(self.view as? UITracingLayerView)?.schedule(layout: { [weak self] in
if let strongSelf = self {
if let (currentRequestId, currentRequestTransition) = strongSelf.scheduledLayoutTransitionRequest, currentRequestId == requestId {
strongSelf.scheduledLayoutTransitionRequest = nil
strongSelf.requestLayout(currentRequestTransition)
}
}
})
self.setNeedsLayout()
}
}