mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
564 lines
28 KiB
Swift
564 lines
28 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import TelegramPresentationData
|
|
import AccountContext
|
|
import LocalAuth
|
|
import AppBundle
|
|
import PasscodeInputFieldNode
|
|
import MonotonicTime
|
|
import GradientBackground
|
|
|
|
private let titleFont = Font.regular(20.0)
|
|
private let subtitleFont = Font.regular(15.0)
|
|
private let buttonFont = Font.regular(17.0)
|
|
|
|
final class PasscodeEntryControllerNode: ASDisplayNode {
|
|
private let accountManager: AccountManager<TelegramAccountManagerTypes>
|
|
private var presentationData: PresentationData
|
|
private var theme: PresentationTheme
|
|
private var strings: PresentationStrings
|
|
private var wallpaper: TelegramWallpaper
|
|
private let passcodeType: PasscodeEntryFieldType
|
|
private let biometricsType: LocalAuthBiometricAuthentication?
|
|
private let arguments: PasscodeEntryControllerPresentationArguments
|
|
private var background: PasscodeBackground?
|
|
|
|
private let modalPresentation: Bool
|
|
|
|
private var backgroundCustomNode: ASDisplayNode?
|
|
private let backgroundDimNode: ASDisplayNode
|
|
private let backgroundImageNode: ASImageNode
|
|
private let iconNode: PasscodeLockIconNode
|
|
private let titleNode: PasscodeEntryLabelNode
|
|
private let inputFieldNode: PasscodeInputFieldNode
|
|
private let subtitleNode: PasscodeEntryLabelNode
|
|
private let keyboardNode: PasscodeEntryKeyboardNode
|
|
private let cancelButtonNode: HighlightableButtonNode
|
|
private let deleteButtonNode: HighlightableButtonNode
|
|
private let biometricButtonNode: HighlightableButtonNode
|
|
private let effectView: UIVisualEffectView
|
|
|
|
private var invalidAttempts: AccessChallengeAttempts?
|
|
private var timer: SwiftSignalKit.Timer?
|
|
|
|
private let hapticFeedback = HapticFeedback()
|
|
|
|
private var validLayout: ContainerViewLayout?
|
|
|
|
var checkPasscode: ((String) -> Void)?
|
|
var requestBiometrics: (() -> Void)?
|
|
|
|
init(accountManager: AccountManager<TelegramAccountManagerTypes>, presentationData: PresentationData, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, passcodeType: PasscodeEntryFieldType, biometricsType: LocalAuthBiometricAuthentication?, arguments: PasscodeEntryControllerPresentationArguments, modalPresentation: Bool) {
|
|
self.accountManager = accountManager
|
|
self.presentationData = presentationData
|
|
self.theme = theme
|
|
self.strings = strings
|
|
self.wallpaper = wallpaper
|
|
self.passcodeType = passcodeType
|
|
self.biometricsType = biometricsType
|
|
self.arguments = arguments
|
|
self.modalPresentation = modalPresentation
|
|
|
|
self.backgroundImageNode = ASImageNode()
|
|
self.backgroundImageNode.contentMode = .scaleToFill
|
|
|
|
self.backgroundDimNode = ASDisplayNode()
|
|
self.backgroundDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.15)
|
|
self.backgroundDimNode.isHidden = true
|
|
|
|
self.iconNode = PasscodeLockIconNode()
|
|
self.titleNode = PasscodeEntryLabelNode()
|
|
self.inputFieldNode = PasscodeInputFieldNode(color: .white, accentColor: .white, fieldType: passcodeType, keyboardAppearance: .dark, useCustomNumpad: true)
|
|
self.subtitleNode = PasscodeEntryLabelNode()
|
|
self.keyboardNode = PasscodeEntryKeyboardNode()
|
|
self.cancelButtonNode = HighlightableButtonNode()
|
|
self.deleteButtonNode = HighlightableButtonNode()
|
|
self.deleteButtonNode.hitTestSlop = UIEdgeInsets(top: -10.0, left: -16.0, bottom: -10.0, right: -16.0)
|
|
self.biometricButtonNode = HighlightableButtonNode()
|
|
self.effectView = UIVisualEffectView(effect: nil)
|
|
|
|
super.init()
|
|
|
|
self.setViewBlock({
|
|
return UITracingLayerView()
|
|
})
|
|
|
|
self.backgroundColor = .clear
|
|
self.iconNode.unlockedColor = theme.rootController.navigationBar.primaryTextColor
|
|
|
|
self.keyboardNode.charactedEntered = { [weak self] character in
|
|
if let strongSelf = self {
|
|
strongSelf.inputFieldNode.append(character)
|
|
if let gradientNode = strongSelf.backgroundCustomNode as? GradientBackgroundNode {
|
|
gradientNode.animateEvent(transition: .animated(duration: 0.55, curve: .spring), extendAnimation: false, backwards: false, completion: {})
|
|
}
|
|
}
|
|
}
|
|
self.keyboardNode.backspace = { [weak self] in
|
|
if let strongSelf = self {
|
|
let _ = strongSelf.inputFieldNode.delete()
|
|
if let gradientNode = strongSelf.backgroundCustomNode as? GradientBackgroundNode {
|
|
gradientNode.animateEvent(transition: .animated(duration: 0.55, curve: .spring), extendAnimation: false, backwards: true, completion: {})
|
|
}
|
|
}
|
|
}
|
|
self.inputFieldNode.complete = { [weak self] passcode in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
if strongSelf.shouldWaitBeforeNextAttempt() {
|
|
strongSelf.animateError()
|
|
} else {
|
|
strongSelf.checkPasscode?(passcode)
|
|
}
|
|
}
|
|
|
|
self.cancelButtonNode.setTitle(strings.Common_Cancel, with: buttonFont, with: .white, for: .normal)
|
|
self.deleteButtonNode.setTitle(strings.Common_Delete, with: buttonFont, with: .white, for: .normal)
|
|
|
|
if let biometricsType = self.biometricsType {
|
|
switch biometricsType {
|
|
case .touchId:
|
|
self.biometricButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/PasscodeTouchId"), color: .white), for: .normal)
|
|
case .faceId:
|
|
self.biometricButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Settings/PasscodeFaceId"), color: .white), for: .normal)
|
|
}
|
|
}
|
|
|
|
self.addSubnode(self.backgroundImageNode)
|
|
self.addSubnode(self.backgroundDimNode)
|
|
self.addSubnode(self.iconNode)
|
|
self.addSubnode(self.titleNode)
|
|
self.addSubnode(self.inputFieldNode)
|
|
self.addSubnode(self.subtitleNode)
|
|
self.addSubnode(self.keyboardNode)
|
|
self.addSubnode(self.deleteButtonNode)
|
|
self.addSubnode(self.biometricButtonNode)
|
|
|
|
if self.arguments.cancel != nil {
|
|
self.addSubnode(self.cancelButtonNode)
|
|
}
|
|
}
|
|
|
|
override func didLoad() {
|
|
super.didLoad()
|
|
|
|
self.view.disablesInteractiveKeyboardGestureRecognizer = true
|
|
|
|
self.view.insertSubview(self.effectView, at: 0)
|
|
|
|
if self.arguments.cancel != nil {
|
|
self.cancelButtonNode.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside)
|
|
}
|
|
self.deleteButtonNode.addTarget(self, action: #selector(self.deletePressed), forControlEvents: .touchUpInside)
|
|
self.biometricButtonNode.addTarget(self, action: #selector(self.biometricsPressed), forControlEvents: .touchUpInside)
|
|
}
|
|
|
|
@objc private func cancelPressed() {
|
|
self.animateOut(down: true)
|
|
self.arguments.cancel?()
|
|
}
|
|
|
|
@objc private func deletePressed() {
|
|
self.hapticFeedback.tap()
|
|
let result = self.inputFieldNode.delete()
|
|
if result, let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
|
|
gradientNode.animateEvent(transition: .animated(duration: 0.55, curve: .spring), extendAnimation: false, backwards: true, completion: {})
|
|
}
|
|
}
|
|
|
|
@objc private func biometricsPressed() {
|
|
self.requestBiometrics?()
|
|
}
|
|
|
|
func activateInput() {
|
|
self.inputFieldNode.activateInput()
|
|
}
|
|
|
|
func updatePresentationData(_ presentationData: PresentationData) {
|
|
self.presentationData = presentationData
|
|
self.theme = presentationData.theme
|
|
self.strings = presentationData.strings
|
|
self.wallpaper = presentationData.chatWallpaper
|
|
|
|
self.deleteButtonNode.setTitle(self.strings.Common_Delete, with: buttonFont, with: .white, for: .normal)
|
|
if let validLayout = self.validLayout {
|
|
self.containerLayoutUpdated(validLayout, navigationBarHeight: 0.0, transition: .immediate)
|
|
}
|
|
}
|
|
|
|
func updateBackground() {
|
|
guard let validLayout = self.validLayout else {
|
|
return
|
|
}
|
|
|
|
let size = validLayout.size
|
|
if let background = self.background, background.size == size {
|
|
return
|
|
}
|
|
|
|
switch self.wallpaper {
|
|
case let .color(colorValue):
|
|
let color = UIColor(argb: colorValue)
|
|
let baseColor: UIColor
|
|
let lightness = color.lightness
|
|
if lightness < 0.1 || lightness > 0.9 {
|
|
baseColor = self.theme.chat.message.outgoing.bubble.withoutWallpaper.fill[0]
|
|
} else{
|
|
baseColor = color
|
|
}
|
|
|
|
let color1: UIColor
|
|
let color2: UIColor
|
|
let color3: UIColor
|
|
let color4: UIColor
|
|
if self.theme.overallDarkAppearance {
|
|
color1 = baseColor.withMultiplied(hue: 1.034, saturation: 0.819, brightness: 0.214)
|
|
color2 = baseColor.withMultiplied(hue: 1.029, saturation: 0.77, brightness: 0.132)
|
|
color3 = color1
|
|
color4 = color2
|
|
} else {
|
|
color1 = baseColor.withMultiplied(hue: 1.029, saturation: 0.312, brightness: 1.26)
|
|
color2 = baseColor.withMultiplied(hue: 1.034, saturation: 0.729, brightness: 0.942)
|
|
color3 = baseColor.withMultiplied(hue: 1.029, saturation: 0.729, brightness: 1.231)
|
|
color4 = baseColor.withMultiplied(hue: 1.034, saturation: 0.583, brightness: 1.043)
|
|
}
|
|
self.background = CustomPasscodeBackground(size: size, colors: [color1, color2, color3, color4], inverted: false)
|
|
case let .gradient(gradient):
|
|
self.background = CustomPasscodeBackground(size: size, colors: gradient.colors.compactMap { UIColor(rgb: $0) }, inverted: (gradient.settings.intensity ?? 0) < 0)
|
|
case .image, .file:
|
|
if let image = chatControllerBackgroundImage(theme: self.theme, wallpaper: self.wallpaper, mediaBox: self.accountManager.mediaBox, composed: false, knockoutMode: false) {
|
|
self.background = ImageBasedPasscodeBackground(image: image, size: size)
|
|
} else {
|
|
if case let .file(file) = self.wallpaper, !file.settings.colors.isEmpty {
|
|
self.background = CustomPasscodeBackground(size: size, colors: file.settings.colors.compactMap { UIColor(rgb: $0) }, inverted: (file.settings.intensity ?? 0) < 0)
|
|
} else {
|
|
self.background = GradientPasscodeBackground(size: size, backgroundColors: self.theme.passcode.backgroundColors.colors, buttonColor: self.theme.passcode.buttonColor)
|
|
}
|
|
}
|
|
default:
|
|
self.background = GradientPasscodeBackground(size: size, backgroundColors: self.theme.passcode.backgroundColors.colors, buttonColor: self.theme.passcode.buttonColor)
|
|
}
|
|
|
|
if let background = self.background {
|
|
self.backgroundCustomNode?.removeFromSupernode()
|
|
self.backgroundCustomNode = nil
|
|
|
|
if let backgroundImage = background.backgroundImage {
|
|
self.backgroundImageNode.image = backgroundImage
|
|
self.backgroundDimNode.isHidden = true
|
|
} else if let customBackgroundNode = background.makeBackgroundNode() {
|
|
self.backgroundCustomNode = customBackgroundNode
|
|
self.insertSubnode(customBackgroundNode, aboveSubnode: self.backgroundImageNode)
|
|
if let background = background as? CustomPasscodeBackground, background.inverted {
|
|
self.backgroundDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.75)
|
|
} else {
|
|
self.backgroundDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.15)
|
|
}
|
|
self.backgroundDimNode.isHidden = false
|
|
}
|
|
self.keyboardNode.updateBackground(self.presentationData, background)
|
|
self.inputFieldNode.updateBackground(background)
|
|
}
|
|
}
|
|
|
|
private let waitInterval: Int32 = 60
|
|
private func shouldWaitBeforeNextAttempt() -> Bool {
|
|
if let attempts = self.invalidAttempts {
|
|
if attempts.count >= 6 {
|
|
var bootTimestamp: Int32 = 0
|
|
let uptime = getDeviceUptimeSeconds(&bootTimestamp)
|
|
|
|
if attempts.bootTimestamp != bootTimestamp {
|
|
return true
|
|
}
|
|
|
|
if uptime - attempts.uptime < waitInterval {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
} else {
|
|
return false
|
|
}
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
|
|
func updateInvalidAttempts(_ attempts: AccessChallengeAttempts?, animated: Bool = false) {
|
|
self.invalidAttempts = attempts
|
|
if let attempts = attempts {
|
|
var text = NSAttributedString(string: "")
|
|
if attempts.count >= 6 && self.shouldWaitBeforeNextAttempt() {
|
|
text = NSAttributedString(string: self.strings.PasscodeSettings_TryAgainIn1Minute, font: subtitleFont, textColor: .white)
|
|
|
|
self.timer?.invalidate()
|
|
let timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
|
|
if let strongSelf = self {
|
|
if !strongSelf.shouldWaitBeforeNextAttempt() {
|
|
strongSelf.updateInvalidAttempts(strongSelf.invalidAttempts, animated: true)
|
|
strongSelf.timer?.invalidate()
|
|
strongSelf.timer = nil
|
|
}
|
|
}
|
|
}, queue: Queue.mainQueue())
|
|
self.timer = timer
|
|
timer.start()
|
|
}
|
|
self.subtitleNode.setAttributedText(text, animation: animated ? .crossFade : .none, completion: {})
|
|
} else {
|
|
self.subtitleNode.setAttributedText(NSAttributedString(string: ""), animation: animated ? .crossFade : .none, completion: {})
|
|
}
|
|
}
|
|
|
|
func hideBiometrics() {
|
|
self.biometricButtonNode.layer.animateScale(from: 1.0, to: 0.00001, duration: 0.25, delay: 0.0, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, completion: { [weak self] _ in
|
|
self?.biometricButtonNode.isHidden = true
|
|
})
|
|
self.animateError()
|
|
}
|
|
|
|
func initialAppearance(fadeIn: Bool = false) {
|
|
if fadeIn {
|
|
let effect = self.theme.overallDarkAppearance ? UIBlurEffect(style: .dark) : UIBlurEffect(style: .light)
|
|
UIView.animate(withDuration: 0.3, animations: {
|
|
if #available(iOS 9.0, *) {
|
|
self.effectView.effect = effect
|
|
} else {
|
|
self.effectView.alpha = 1.0
|
|
}
|
|
})
|
|
self.backgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
|
if let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
|
|
gradientNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
|
self.backgroundDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
|
gradientNode.animateEvent(transition: .animated(duration: 1.0, curve: .spring), extendAnimation: true, backwards: false, completion: {})
|
|
}
|
|
}
|
|
self.titleNode.setAttributedText(NSAttributedString(string: self.strings.EnterPasscode_EnterPasscode, font: titleFont, textColor: .white), animation: .none)
|
|
}
|
|
|
|
func animateIn(iconFrame: CGRect, completion: @escaping () -> Void = {}) {
|
|
let effect = self.theme.overallDarkAppearance ? UIBlurEffect(style: .dark) : UIBlurEffect(style: .light)
|
|
UIView.animate(withDuration: 0.3, animations: {
|
|
if #available(iOS 9.0, *) {
|
|
self.effectView.effect = effect
|
|
} else {
|
|
self.effectView.alpha = 1.0
|
|
}
|
|
})
|
|
self.backgroundImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
|
if let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
|
|
gradientNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
|
gradientNode.animateEvent(transition: .animated(duration: 0.35, curve: .spring), extendAnimation: false, backwards: false, completion: {})
|
|
self.backgroundDimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
|
}
|
|
if !iconFrame.isEmpty {
|
|
self.iconNode.animateIn(fromScale: 0.416)
|
|
self.iconNode.layer.animatePosition(from: iconFrame.center.offsetBy(dx: 6.0, dy: 6.0), to: self.iconNode.layer.position, duration: 0.45)
|
|
|
|
Queue.mainQueue().after(0.45) {
|
|
self.hapticFeedback.impact(.medium)
|
|
}
|
|
}
|
|
|
|
self.subtitleNode.isHidden = true
|
|
self.inputFieldNode.isHidden = true
|
|
self.keyboardNode.isHidden = true
|
|
self.cancelButtonNode.isHidden = true
|
|
self.deleteButtonNode.isHidden = true
|
|
self.biometricButtonNode.isHidden = true
|
|
|
|
self.titleNode.setAttributedText(NSAttributedString(string: self.strings.Passcode_AppLockedAlert.replacingOccurrences(of: "\n", with: " "), font: titleFont, textColor: .white), animation: .slideIn, completion: {
|
|
self.subtitleNode.isHidden = false
|
|
self.inputFieldNode.isHidden = false
|
|
self.keyboardNode.isHidden = false
|
|
self.cancelButtonNode.isHidden = false
|
|
self.deleteButtonNode.isHidden = false
|
|
self.biometricButtonNode.isHidden = false
|
|
|
|
self.subtitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
|
|
|
if let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
|
|
gradientNode.animateEvent(transition: .animated(duration: 1.0, curve: .spring), extendAnimation: false, backwards: false, completion: {})
|
|
}
|
|
self.inputFieldNode.animateIn()
|
|
self.keyboardNode.animateIn()
|
|
var biometricDelay = 0.3
|
|
if case .alphanumeric = self.passcodeType {
|
|
biometricDelay = 0.0
|
|
} else {
|
|
self.cancelButtonNode.layer.animateScale(from: 0.0001, to: 1.0, duration: 0.25, delay: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
|
self.deleteButtonNode.layer.animateScale(from: 0.0001, to: 1.0, duration: 0.25, delay: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
|
}
|
|
self.biometricButtonNode.layer.animateScale(from: 0.0001, to: 1.0, duration: 0.25, delay: biometricDelay, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
|
|
|
Queue.mainQueue().after(1.5, {
|
|
self.titleNode.setAttributedText(NSAttributedString(string: self.strings.EnterPasscode_EnterPasscode, font: titleFont, textColor: .white), animation: .crossFade)
|
|
if let validLayout = self.validLayout {
|
|
self.containerLayoutUpdated(validLayout, navigationBarHeight: 0.0, transition: .animated(duration: 0.5, curve: .easeInOut))
|
|
}
|
|
})
|
|
|
|
completion()
|
|
})
|
|
}
|
|
|
|
func animateOut(down: Bool = false, completion: @escaping () -> Void = {}) {
|
|
self.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: down ? self.bounds.size.height : -self.bounds.size.height), duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in
|
|
completion()
|
|
})
|
|
}
|
|
|
|
func animateSuccess() {
|
|
self.iconNode.animateUnlock()
|
|
self.inputFieldNode.animateSuccess()
|
|
}
|
|
|
|
func animateError() {
|
|
self.inputFieldNode.reset()
|
|
self.inputFieldNode.layer.addShakeAnimation(amplitude: -30.0, duration: 0.5, count: 6, decay: true)
|
|
self.iconNode.layer.addShakeAnimation(amplitude: -8.0, duration: 0.5, count: 6, decay: true)
|
|
|
|
self.hapticFeedback.error()
|
|
|
|
if let gradientNode = self.backgroundCustomNode as? GradientBackgroundNode {
|
|
gradientNode.animateEvent(transition: .animated(duration: 1.5, curve: .spring), extendAnimation: true, backwards: true, completion: {})
|
|
}
|
|
}
|
|
|
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
self.validLayout = layout
|
|
|
|
self.updateBackground()
|
|
|
|
let bounds = CGRect(origin: CGPoint(), size: layout.size)
|
|
transition.updateFrame(node: self.backgroundImageNode, frame: bounds)
|
|
transition.updateFrame(node: self.backgroundDimNode, frame: bounds)
|
|
if let backgroundCustomNode = self.backgroundCustomNode {
|
|
transition.updateFrame(node: backgroundCustomNode, frame: bounds)
|
|
if let gradientBackgroundNode = backgroundCustomNode as? GradientBackgroundNode {
|
|
gradientBackgroundNode.updateLayout(size: bounds.size, transition: transition, extendAnimation: false, backwards: false, completion: {})
|
|
}
|
|
}
|
|
transition.updateFrame(view: self.effectView, frame: bounds)
|
|
|
|
switch self.passcodeType {
|
|
case .digits6, .digits4:
|
|
self.keyboardNode.alpha = 1.0
|
|
self.deleteButtonNode.alpha = 1.0
|
|
case .alphanumeric:
|
|
self.keyboardNode.alpha = 0.0
|
|
self.deleteButtonNode.alpha = 0.0
|
|
}
|
|
|
|
let isLandscape = layout.orientation == .landscape && layout.deviceMetrics.type != .tablet
|
|
let keyboardHidden = self.keyboardNode.alpha == 0.0
|
|
|
|
let layoutSize: CGSize
|
|
if isLandscape {
|
|
if keyboardHidden {
|
|
layoutSize = CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right, height: layout.size.height)
|
|
} else {
|
|
layoutSize = CGSize(width: layout.size.width / 2.0, height: layout.size.height)
|
|
}
|
|
} else {
|
|
layoutSize = layout.size
|
|
}
|
|
|
|
if layout.size.width == 320.0 || (isLandscape && keyboardHidden) {
|
|
self.iconNode.alpha = 0.0
|
|
} else {
|
|
self.iconNode.alpha = 1.0
|
|
}
|
|
|
|
let passcodeLayout = PasscodeLayout(layout: layout, modalPresentation: self.modalPresentation)
|
|
let inputFieldOffset: CGFloat
|
|
if isLandscape {
|
|
let bottomInset = layout.inputHeight ?? 0.0
|
|
if !keyboardHidden || bottomInset == 0.0 {
|
|
inputFieldOffset = floor(layoutSize.height / 2.0 + 12.0)
|
|
} else {
|
|
inputFieldOffset = floor(layoutSize.height - bottomInset) / 2.0 - 40.0
|
|
}
|
|
} else {
|
|
inputFieldOffset = passcodeLayout.inputFieldOffset
|
|
}
|
|
|
|
let inputFieldFrame = self.inputFieldNode.updateLayout(size: layoutSize, topOffset: inputFieldOffset, transition: transition)
|
|
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left, y: 0.0), size: layoutSize))
|
|
|
|
let titleFrame: CGRect
|
|
if isLandscape {
|
|
let titleSize = self.titleNode.updateLayout(size: CGSize(width: layoutSize.width, height: layout.size.height), transition: transition)
|
|
titleFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left, y: inputFieldFrame.minY - titleSize.height - 16.0), size: titleSize)
|
|
} else {
|
|
let titleSize = self.titleNode.updateLayout(size: layout.size, transition: transition)
|
|
titleFrame = CGRect(origin: CGPoint(x: 0.0, y: passcodeLayout.titleOffset), size: titleSize)
|
|
}
|
|
transition.updateFrame(node: self.titleNode, frame: titleFrame)
|
|
|
|
let iconSize = CGSize(width: 35.0, height: 37.0)
|
|
let iconFrame: CGRect
|
|
if isLandscape {
|
|
iconFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left + floor((layoutSize.width - iconSize.width) / 2.0) + 6.0, y: titleFrame.minY - iconSize.height - 14.0), size: iconSize)
|
|
} else {
|
|
iconFrame = CGRect(origin: CGPoint(x: floor((layoutSize.width - iconSize.width) / 2.0) + 6.0, y: layout.insets(options: .statusBar).top + 15.0), size: iconSize)
|
|
}
|
|
transition.updateFrame(node: self.iconNode, frame: iconFrame)
|
|
|
|
var subtitleOffset = passcodeLayout.subtitleOffset
|
|
if case .alphanumeric = self.passcodeType {
|
|
subtitleOffset = 16.0
|
|
}
|
|
let subtitleSize = self.subtitleNode.updateLayout(size: layoutSize, transition: transition)
|
|
transition.updateFrame(node: self.subtitleNode, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left, y: inputFieldFrame.maxY + subtitleOffset), size: subtitleSize))
|
|
|
|
let (keyboardFrame, keyboardButtonSize) = self.keyboardNode.updateLayout(layout: passcodeLayout, transition: transition)
|
|
transition.updateFrame(node: self.keyboardNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: layout.size))
|
|
|
|
let bottomInset = layout.inputHeight ?? 0.0
|
|
|
|
let cancelSize = self.cancelButtonNode.measure(layout.size)
|
|
var bottomButtonY = layout.size.height - layout.intrinsicInsets.bottom - cancelSize.height - passcodeLayout.keyboard.deleteOffset
|
|
var cancelX = floor(keyboardFrame.minX + keyboardButtonSize.width / 2.0 - cancelSize.width / 2.0)
|
|
var cancelY = bottomButtonY
|
|
if bottomInset > 0 && keyboardHidden {
|
|
cancelX = floor((layout.size.width - cancelSize.width) / 2.0)
|
|
cancelY = layout.size.height - bottomInset - cancelSize.height - 15.0 - layout.intrinsicInsets.bottom
|
|
} else if isLandscape {
|
|
bottomButtonY = keyboardFrame.maxY - keyboardButtonSize.height + floor((keyboardButtonSize.height - cancelSize.height) / 2.0)
|
|
cancelY = bottomButtonY
|
|
}
|
|
|
|
transition.updateFrame(node: self.cancelButtonNode, frame: CGRect(origin: CGPoint(x: cancelX, y: cancelY), size: cancelSize))
|
|
|
|
let deleteSize = self.deleteButtonNode.measure(layout.size)
|
|
transition.updateFrame(node: self.deleteButtonNode, frame: CGRect(origin: CGPoint(x: floor(keyboardFrame.maxX - keyboardButtonSize.width / 2.0 - deleteSize.width / 2.0), y: bottomButtonY), size: deleteSize))
|
|
|
|
if let biometricIcon = self.biometricButtonNode.image(for: .normal) {
|
|
var biometricX = layout.safeInsets.left + floor((layoutSize.width - biometricIcon.size.width) / 2.0)
|
|
var biometricY: CGFloat = 0.0
|
|
if isLandscape {
|
|
if bottomInset > 0 && keyboardHidden {
|
|
biometricX = cancelX + cancelSize.width + 64.0
|
|
}
|
|
biometricY = cancelY + floor((cancelSize.height - biometricIcon.size.height) / 2.0)
|
|
} else {
|
|
if bottomInset > 0 && keyboardHidden {
|
|
biometricY = inputFieldFrame.maxY + floor((layout.size.height - bottomInset - inputFieldFrame.maxY - biometricIcon.size.height) / 2.0)
|
|
} else {
|
|
biometricY = keyboardFrame.maxY + passcodeLayout.keyboard.biometricsOffset
|
|
}
|
|
}
|
|
transition.updateFrame(node: self.biometricButtonNode, frame: CGRect(origin: CGPoint(x: biometricX, y: biometricY), size: biometricIcon.size))
|
|
}
|
|
}
|
|
}
|