mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
277 lines
14 KiB
Swift
277 lines
14 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import TelegramCore
|
|
import SyncCore
|
|
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(size: layout.size, 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?()
|
|
}
|
|
}
|