mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Refactor PasscodeUI
This commit is contained in:
275
submodules/PasscodeUI/Sources/PasscodeSetupControllerNode.swift
Normal file
275
submodules/PasscodeUI/Sources/PasscodeSetupControllerNode.swift
Normal file
@@ -0,0 +1,275 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
|
||||
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: PasscodeEntryInputFieldNode
|
||||
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 = PasscodeEntryInputFieldNode(color: self.presentationData.theme.list.itemPrimaryTextColor, accentColor: self.presentationData.theme.list.itemAccentColor, fieldType: passcodeType, keyboardAppearance: self.presentationData.theme.chatList.searchBarKeyboardColor.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 passcodeLayout = PasscodeLayout(layout: layout, titleOffset: 0.0, subtitleOffset: 0.0, inputFieldOffset: floor(insets.top + navigationBarHeight + (layout.size.height - navigationBarHeight - insets.top - insets.bottom - 24.0) / 2.0))
|
||||
let inputFieldFrame = self.inputFieldNode.updateLayout(layout: passcodeLayout, 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?()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user