Swiftgram/submodules/PassportUI/Sources/SecureIdAuthPasswordOptionContentNode.swift
2019-08-30 23:32:32 +04:00

221 lines
10 KiB
Swift

import Foundation
import UIKit
import AsyncDisplayKit
import Display
import TelegramPresentationData
import ActivityIndicator
import AppBundle
private let passwordFont = Font.regular(16.0)
private let buttonFont = Font.regular(17.0)
final class SecureIdAuthPasswordOptionContentNode: ASDisplayNode, SecureIdAuthContentNode, UITextFieldDelegate {
private let checkPassword: (String) -> Void
private let passwordHelp: () -> Void
private let inputContainer: ASDisplayNode
private let titleNode: ImmediateTextNode
private let inputBackground: ASImageNode
private let inputField: TextFieldNode
private var clearOnce: Bool = false
private let inputButtonNode: HighlightableButtonNode
private let inputActivityNode: ActivityIndicator
private let buttonNode: HighlightableButtonNode
private let buttonBackground: ASImageNode
private let buttonLabel: ImmediateTextNode
private var validLayout: CGFloat?
private var isChecking = false
private let hapticFeedback = HapticFeedback()
init(theme: PresentationTheme, strings: PresentationStrings, hint: String, checkPassword: @escaping (String) -> Void, passwordHelp: @escaping () -> Void) {
self.checkPassword = checkPassword
self.passwordHelp = passwordHelp
self.inputContainer = ASDisplayNode()
self.inputBackground = ASImageNode()
self.inputBackground.isLayerBacked = true
self.inputBackground.displaysAsynchronously = false
self.inputBackground.displayWithoutProcessing = true
self.titleNode = ImmediateTextNode()
self.titleNode.attributedText = NSAttributedString(string: strings.Passport_PasswordHelp, font: Font.regular(14.0), textColor: theme.list.freeTextColor)
self.titleNode.maximumNumberOfLines = 0
self.titleNode.textAlignment = .center
self.inputField = TextFieldNode()
self.inputButtonNode = HighlightableButtonNode()
self.inputActivityNode = ActivityIndicator(type: .custom(theme.list.freeInputField.controlColor, 18.0, 1.5, false))
if let image = generateTintedImage(image: UIImage(bundleImageName: "Secure ID/PasswordHelpIcon"), color: theme.list.freeInputField.controlColor) {
self.inputButtonNode.setImage(image, for: [])
self.inputButtonNode.frame = CGRect(origin: CGPoint(), size: image.size)
}
self.inputBackground.image = generateStretchableFilledCircleImage(radius: 10.0, color: theme.list.freeInputField.backgroundColor)
self.inputField.textField.isSecureTextEntry = true
self.inputField.textField.font = passwordFont
self.inputField.textField.textColor = theme.list.freeInputField.primaryColor
self.inputField.textField.attributedPlaceholder = NSAttributedString(string: hint.isEmpty ? strings.LoginPassword_PasswordPlaceholder : hint, font: passwordFont, textColor: theme.list.freeInputField.placeholderColor)
self.inputField.textField.keyboardAppearance = theme.rootController.keyboardColor.keyboardAppearance
self.inputField.textField.tintColor = theme.list.itemAccentColor
self.buttonNode = HighlightableButtonNode()
self.buttonBackground = ASImageNode()
self.buttonBackground.isLayerBacked = true
self.buttonBackground.displaysAsynchronously = false
self.buttonBackground.displayWithoutProcessing = true
self.buttonBackground.image = generateStretchableFilledCircleImage(radius: 10.0, color: theme.list.itemCheckColors.fillColor)
self.buttonNode.addSubnode(self.buttonBackground)
self.buttonLabel = ImmediateTextNode()
self.buttonLabel.attributedText = NSAttributedString(string: strings.Common_Next, font: buttonFont, textColor: theme.list.itemCheckColors.foregroundColor)
self.buttonNode.addSubnode(self.buttonLabel)
super.init()
self.inputContainer.addSubnode(self.titleNode)
self.inputContainer.addSubnode(self.inputBackground)
self.inputContainer.addSubnode(self.inputField)
self.inputContainer.addSubnode(self.inputButtonNode)
self.inputContainer.addSubnode(self.inputActivityNode)
self.inputContainer.addSubnode(self.buttonNode)
self.addSubnode(self.inputContainer)
self.buttonNode.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.buttonBackground.layer.removeAnimation(forKey: "opacity")
strongSelf.buttonBackground.alpha = 0.55
} else {
strongSelf.buttonBackground.alpha = 1.0
strongSelf.buttonBackground.layer.animateAlpha(from: 0.55, to: 1.0, duration: 0.2)
}
}
}
self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
self.inputField.textField.delegate = self
self.inputButtonNode.hitTestSlop = UIEdgeInsets(top: -4.0, left: -4.0, bottom: -4.0, right: -4.0)
self.inputButtonNode.addTarget(self, action: #selector(self.inputButtonPressed), forControlEvents: .touchUpInside)
self.inputActivityNode.isHidden = true
}
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> SecureIdAuthContentLayout {
let transition = self.validLayout == nil ? .immediate : transition
self.validLayout = width
let inputWidth = min(270.0, width - 30.0)
let labelSize = self.buttonLabel.updateLayout(CGSize(width: width - 20.0, height: 100.0))
let buttonSize = CGSize(width: max(labelSize.width + 30.0, 100.0), height: 36.0)
let titleSpacing: CGFloat = 15.0
let titleSize = self.titleNode.updateLayout(CGSize(width: inputWidth, height: CGFloat.greatestFiniteMagnitude))
let buttonSpacing: CGFloat = 16.0
let inputFrame = CGRect(origin: CGPoint(x: floor((width - inputWidth) / 2.0), y: titleSize.height + titleSpacing), size: CGSize(width: inputWidth, height: 32.0))
let inputContainerFrame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: titleSize.height + titleSpacing + inputFrame.height + buttonSpacing + buttonSize.height))
let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: 0.0), size: titleSize)
transition.updateFrame(node: self.inputContainer, frame: inputContainerFrame)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
transition.updateFrame(node: self.inputBackground, frame: inputFrame)
var inputFieldFrame = inputFrame.insetBy(dx: 6.0, dy: 0.0)
inputFieldFrame.size.width -= 16.0
transition.updateFrame(node: self.inputField, frame: inputFieldFrame)
transition.updateFrame(node: self.inputButtonNode, frame: CGRect(origin: CGPoint(x: inputFrame.maxX - self.inputButtonNode.bounds.size.width - 6.0, y: inputFrame.minY + floor((inputFrame.height - self.inputButtonNode.bounds.size.height) / 2.0)), size: self.inputButtonNode.bounds.size))
let activitySize = CGSize(width: 18.0, height: 18.0)
transition.updateFrame(node: self.inputActivityNode, frame: CGRect(origin: CGPoint(x: inputFrame.maxX - activitySize.width - 6.0, y: inputFrame.minY + floor((inputFrame.height - activitySize.height) / 2.0)), size: activitySize))
let buttonBounds = CGRect(origin: CGPoint(), size: buttonSize)
transition.updateFrame(node: self.buttonNode, frame: buttonBounds.offsetBy(dx: floor((width - buttonSize.width) / 2.0), dy: inputFrame.maxY + buttonSpacing))
transition.updateFrame(node: self.buttonBackground, frame: buttonBounds)
transition.updateFrame(node: self.buttonLabel, frame: CGRect(origin: CGPoint(x: floor((buttonSize.width - labelSize.width) / 2.0), y: floor((buttonSize.height - labelSize.height) / 2.0)), size: buttonSize))
return SecureIdAuthContentLayout(height: inputContainerFrame.size.height, centerOffset: floor((inputContainerFrame.size.height) / 2.0))
}
func animateIn() {
self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
}
func animateOut(completion: @escaping () -> Void) {
self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in
completion()
})
}
func didAppear() {
self.inputField.textField.becomeFirstResponder()
}
func willDisappear() {
self.inputField.textField.resignFirstResponder()
}
@objc private func buttonPressed() {
if self.isChecking {
return
}
if self.inputField.textField.text?.isEmpty ?? true {
self.inputField.layer.addShakeAnimation()
self.inputBackground.layer.addShakeAnimation()
self.hapticFeedback.error()
} else {
self.checkPassword(self.inputField.textField.text ?? "")
}
}
@objc private func inputButtonPressed() {
self.passwordHelp()
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if self.isChecking {
return false
}
if self.clearOnce {
self.clearOnce = false
if range.length > string.count {
textField.text = ""
return false
}
}
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if !self.isChecking {
self.buttonPressed()
}
return false
}
func updateIsChecking(_ isChecking: Bool) {
self.isChecking = isChecking
self.inputField.alpha = isChecking ? 0.5 : 1.0
self.inputActivityNode.isHidden = !isChecking
self.inputButtonNode.isHidden = isChecking
}
func updateIsInvalid() {
self.clearOnce = true
}
}