Swiftgram/submodules/PasscodeUI/Sources/PasscodeSetupControllerNode.swift
2019-09-21 02:59:11 +03:00

276 lines
14 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramCore
import TelegramPresentationData
import PasscodeInputFieldNode
enum PasscodeSetupInitialState {
case createPasscode
case changePassword(current: String, hasRecoveryEmail: Bool, hasSecureValues: Bool)
}
enum PasscodeSetupStateKind: Int32 {
case enterPasscode
case confirmPasscode
}
private func generateFieldBackground(backgroundColor: UIColor, borderColor: UIColor) -> UIImage? {
return generateImage(CGSize(width: 1.0, height: 48.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.setFillColor(backgroundColor.cgColor)
context.fill(bounds)
context.setFillColor(borderColor.cgColor)
context.fill(CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: UIScreenPixel)))
context.fill(CGRect(origin: CGPoint(x: 0.0, y: size.height - UIScreenPixel), size: CGSize(width: 1.0, height: UIScreenPixel)))
})
}
final class PasscodeSetupControllerNode: ASDisplayNode {
private var presentationData: PresentationData
private var mode: PasscodeSetupControllerMode
private let wrapperNode: ASDisplayNode
private let titleNode: ASTextNode
private let subtitleNode: ASTextNode
private let inputFieldNode: PasscodeInputFieldNode
private let inputFieldBackgroundNode: ASImageNode
private let modeButtonNode: HighlightableButtonNode
var previousPasscode: String?
var currentPasscode: String {
return self.inputFieldNode.text
}
var selectPasscodeMode: (() -> Void)?
var checkPasscode: ((String) -> Bool)?
var complete: ((String, Bool) -> Void)?
var updateNextAction: ((Bool) -> Void)?
private let hapticFeedback = HapticFeedback()
private var validLayout: (ContainerViewLayout, CGFloat)?
private var maxBottomInset: CGFloat?
init(presentationData: PresentationData, mode: PasscodeSetupControllerMode) {
self.presentationData = presentationData
self.mode = mode
self.wrapperNode = ASDisplayNode()
self.titleNode = ASTextNode()
self.titleNode.isUserInteractionEnabled = false
self.titleNode.displaysAsynchronously = false
self.subtitleNode = ASTextNode()
self.subtitleNode.isUserInteractionEnabled = false
self.subtitleNode.displaysAsynchronously = false
let passcodeType: PasscodeEntryFieldType
switch self.mode {
case let .entry(challenge):
switch challenge {
case let .numericalPassword(value, _, _):
passcodeType = value.count == 6 ? .digits6 : .digits4
default:
passcodeType = .alphanumeric
}
case .setup:
passcodeType = .digits6
}
self.inputFieldNode = PasscodeInputFieldNode(color: self.presentationData.theme.list.itemPrimaryTextColor, accentColor: self.presentationData.theme.list.itemAccentColor, fieldType: passcodeType, keyboardAppearance: self.presentationData.theme.rootController.keyboardColor.keyboardAppearance)
self.inputFieldBackgroundNode = ASImageNode()
self.inputFieldBackgroundNode.alpha = passcodeType == .alphanumeric ? 1.0 : 0.0
self.inputFieldBackgroundNode.contentMode = .scaleToFill
self.inputFieldBackgroundNode.image = generateFieldBackground(backgroundColor: self.presentationData.theme.list.itemBlocksBackgroundColor, borderColor: self.presentationData.theme.list.itemBlocksSeparatorColor)
self.modeButtonNode = HighlightableButtonNode()
self.modeButtonNode.setTitle(self.presentationData.strings.PasscodeSettings_PasscodeOptions, with: Font.regular(17.0), with: self.presentationData.theme.list.itemAccentColor, for: .normal)
super.init()
self.setViewBlock({
return UITracingLayerView()
})
self.backgroundColor = self.presentationData.theme.list.blocksBackgroundColor
self.addSubnode(self.wrapperNode)
self.wrapperNode.addSubnode(self.titleNode)
self.wrapperNode.addSubnode(self.subtitleNode)
self.wrapperNode.addSubnode(self.inputFieldBackgroundNode)
self.wrapperNode.addSubnode(self.inputFieldNode)
self.wrapperNode.addSubnode(self.modeButtonNode)
let text: String
switch self.mode {
case .entry:
self.modeButtonNode.isHidden = true
self.modeButtonNode.isAccessibilityElement = false
text = self.presentationData.strings.EnterPasscode_EnterPasscode
case let .setup(change, _):
if change {
text = self.presentationData.strings.EnterPasscode_EnterNewPasscodeChange
} else {
text = self.presentationData.strings.EnterPasscode_EnterNewPasscodeNew
}
}
self.titleNode.attributedText = NSAttributedString(string: text, font: Font.regular(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
self.inputFieldNode.complete = { [weak self] passcode in
self?.activateNext()
}
self.modeButtonNode.addTarget(self, action: #selector(self.modePressed), forControlEvents: .touchUpInside)
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight)
var insets = layout.insets(options: [.statusBar, .input])
if let maxBottomInset = self.maxBottomInset {
if maxBottomInset > insets.bottom {
insets.bottom = maxBottomInset
} else {
self.maxBottomInset = insets.bottom
}
} else {
self.maxBottomInset = insets.bottom
}
self.wrapperNode.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)
let inputFieldFrame = self.inputFieldNode.updateLayout(layout: layout, topOffset: floor(insets.top + navigationBarHeight + (layout.size.height - navigationBarHeight - insets.top - insets.bottom - 24.0) / 2.0), transition: transition)
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(origin: CGPoint(), size: layout.size))
transition.updateFrame(node: self.inputFieldBackgroundNode, frame: CGRect(x: 0.0, y: inputFieldFrame.minY - 6.0, width: layout.size.width, height: 48.0))
let titleSize = self.titleNode.measure(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude))
transition.updateFrame(node: self.titleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: inputFieldFrame.minY - titleSize.height - 20.0), size: titleSize))
let subtitleSize = self.subtitleNode.measure(CGSize(width: layout.size.width - 28.0, height: CGFloat.greatestFiniteMagnitude))
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - subtitleSize.width) / 2.0), y: inputFieldFrame.maxY + 20.0), size: subtitleSize))
transition.updateFrame(node: self.modeButtonNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - insets.bottom - 53.0), size: CGSize(width: layout.size.width, height: 44.0)))
}
func updateMode(_ mode: PasscodeSetupControllerMode) {
self.mode = mode
self.inputFieldNode.reset()
if case let .setup(_, type) = mode {
self.inputFieldNode.updateFieldType(type, animated: true)
let fieldBackgroundAlpha: CGFloat
if case .alphanumeric = type {
fieldBackgroundAlpha = 1.0
self.updateNextAction?(true)
} else {
fieldBackgroundAlpha = 0.0
self.updateNextAction?(false)
}
let previousAlpha = self.inputFieldBackgroundNode.alpha
self.inputFieldBackgroundNode.alpha = fieldBackgroundAlpha
self.inputFieldBackgroundNode.layer.animateAlpha(from: previousAlpha, to: fieldBackgroundAlpha, duration: 0.25)
self.subtitleNode.isHidden = true
}
}
func activateNext() {
guard !self.currentPasscode.isEmpty else {
self.animateError()
return
}
switch self.mode {
case .entry:
if !(self.checkPasscode?(self.currentPasscode) ?? false) {
self.animateError()
}
case .setup:
if let previousPasscode = self.previousPasscode {
if self.currentPasscode == previousPasscode {
var numerical = false
if case let .setup(_, type) = mode {
if case .alphanumeric = type {
} else {
numerical = true
}
}
self.complete?(self.currentPasscode, numerical)
} else {
self.previousPasscode = nil
if let snapshotView = self.wrapperNode.view.snapshotContentTree() {
snapshotView.frame = self.wrapperNode.frame
self.wrapperNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.wrapperNode.view)
snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: self.wrapperNode.bounds.width, y: 0.0), duration: 0.25, removeOnCompletion: false, additive: true, completion : { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
self.wrapperNode.layer.animatePosition(from: CGPoint(x: -self.wrapperNode.bounds.width, y: 0.0), to: CGPoint(), duration: 0.25, additive: true)
self.inputFieldNode.reset(animated: false)
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.EnterPasscode_EnterNewPasscodeChange, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
self.subtitleNode.isHidden = false
self.subtitleNode.attributedText = NSAttributedString(string: self.presentationData.strings.PasscodeSettings_DoNotMatch, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
self.modeButtonNode.isHidden = false
self.modeButtonNode.isAccessibilityElement = true
UIAccessibility.post(notification: UIAccessibility.Notification.announcement, argument: self.presentationData.strings.PasscodeSettings_DoNotMatch)
if let validLayout = self.validLayout {
self.containerLayoutUpdated(validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate)
}
}
}
} else {
self.previousPasscode = self.currentPasscode
if let snapshotView = self.wrapperNode.view.snapshotContentTree() {
snapshotView.frame = self.wrapperNode.frame
self.wrapperNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.wrapperNode.view)
snapshotView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -self.wrapperNode.bounds.width, y: 0.0), duration: 0.25, removeOnCompletion: false, additive: true, completion : { [weak snapshotView] _ in
snapshotView?.removeFromSuperview()
})
self.wrapperNode.layer.animatePosition(from: CGPoint(x: self.wrapperNode.bounds.width, y: 0.0), to: CGPoint(), duration: 0.25, additive: true)
self.inputFieldNode.reset(animated: false)
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.EnterPasscode_RepeatNewPasscode, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
self.subtitleNode.isHidden = true
self.modeButtonNode.isHidden = true
self.modeButtonNode.isAccessibilityElement = false
UIAccessibility.post(notification: UIAccessibility.Notification.announcement, argument: self.presentationData.strings.EnterPasscode_RepeatNewPasscode)
if let validLayout = self.validLayout {
self.containerLayoutUpdated(validLayout.0, navigationBarHeight: validLayout.1, transition: .immediate)
}
}
}
}
}
func activateInput() {
self.inputFieldNode.activateInput()
UIAccessibility.post(notification: UIAccessibility.Notification.announcement, argument: self.titleNode.attributedText?.string)
}
func animateError() {
self.inputFieldNode.reset()
self.inputFieldNode.layer.addShakeAnimation(amplitude: -30.0, duration: 0.5, count: 6, decay: true)
self.hapticFeedback.error()
}
@objc func modePressed() {
self.selectPasscodeMode?()
}
}