mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
370 lines
15 KiB
Swift
370 lines
15 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import SwiftSignalKit
|
|
import TelegramPresentationData
|
|
import GradientBackground
|
|
|
|
private let regularTitleFont = Font.regular(36.0)
|
|
private let regularSubtitleFont: UIFont = {
|
|
return UIFont.systemFont(ofSize: 10.0, weight: UIFont.Weight.bold)
|
|
}()
|
|
|
|
private let largeTitleFont = Font.regular(40.0)
|
|
private let largeSubtitleFont: UIFont = {
|
|
return UIFont.systemFont(ofSize: 12.0, weight: UIFont.Weight.bold)
|
|
}()
|
|
|
|
private func generateButtonImage(background: PasscodeBackground, frame: CGRect, title: String, subtitle: String, highlighted: Bool) -> UIImage? {
|
|
return generateImage(frame.size, contextGenerator: { size, context in
|
|
let bounds = CGRect(origin: CGPoint(), size: size)
|
|
context.clear(bounds)
|
|
|
|
let relativeFrame = CGRect(x: -frame.minX, y: frame.minY - background.size.height + frame.size.height
|
|
, width: background.size.width, height: background.size.height)
|
|
|
|
context.beginPath()
|
|
context.addEllipse(in: bounds)
|
|
context.clip()
|
|
|
|
context.setAlpha(0.8)
|
|
if let foregroundImage = background.foregroundImage {
|
|
context.draw(foregroundImage.cgImage!, in: relativeFrame)
|
|
}
|
|
if highlighted {
|
|
context.setFillColor(UIColor(white: 1.0, alpha: 0.65).cgColor)
|
|
context.fillEllipse(in: bounds)
|
|
}
|
|
|
|
context.setAlpha(1.0)
|
|
context.textMatrix = .identity
|
|
|
|
let titleFont: UIFont
|
|
let subtitleFont: UIFont
|
|
let titleOffset: CGFloat
|
|
let subtitleOffset: CGFloat
|
|
if size.width > 80.0 {
|
|
titleFont = largeTitleFont
|
|
subtitleFont = largeSubtitleFont
|
|
if subtitle.isEmpty {
|
|
titleOffset = -18.0
|
|
} else {
|
|
titleOffset = -11.0
|
|
}
|
|
subtitleOffset = -54.0
|
|
} else if size.width > 70.0 {
|
|
titleFont = regularTitleFont
|
|
subtitleFont = regularSubtitleFont
|
|
if subtitle.isEmpty {
|
|
titleOffset = -17.0
|
|
} else {
|
|
titleOffset = -10.0
|
|
}
|
|
subtitleOffset = -48.0
|
|
}
|
|
else {
|
|
titleFont = regularTitleFont
|
|
subtitleFont = regularSubtitleFont
|
|
if subtitle.isEmpty {
|
|
titleOffset = -11.0
|
|
} else {
|
|
titleOffset = -4.0
|
|
}
|
|
subtitleOffset = -41.0
|
|
}
|
|
|
|
let titlePath = CGMutablePath()
|
|
titlePath.addRect(bounds.offsetBy(dx: 0.0, dy: titleOffset))
|
|
let titleString = NSAttributedString(string: title, font: titleFont, textColor: .white, paragraphAlignment: .center)
|
|
let titleFramesetter = CTFramesetterCreateWithAttributedString(titleString as CFAttributedString)
|
|
let titleFrame = CTFramesetterCreateFrame(titleFramesetter, CFRangeMake(0, titleString.length), titlePath, nil)
|
|
CTFrameDraw(titleFrame, context)
|
|
|
|
if !subtitle.isEmpty {
|
|
let subtitlePath = CGMutablePath()
|
|
subtitlePath.addRect(bounds.offsetBy(dx: 0.0, dy: subtitleOffset))
|
|
let subtitleString = NSAttributedString(string: subtitle, font: subtitleFont, textColor: .white, paragraphAlignment: .center)
|
|
let subtitleFramesetter = CTFramesetterCreateWithAttributedString(subtitleString as CFAttributedString)
|
|
let subtitleFrame = CTFramesetterCreateFrame(subtitleFramesetter, CFRangeMake(0, subtitleString.length), subtitlePath, nil)
|
|
CTFrameDraw(subtitleFrame, context)
|
|
}
|
|
})
|
|
}
|
|
|
|
final class PasscodeEntryButtonNode: HighlightTrackingButtonNode {
|
|
private var presentationData: PresentationData
|
|
private var background: PasscodeBackground
|
|
let title: String
|
|
private let subtitle: String
|
|
|
|
private var currentImage: UIImage?
|
|
private var regularImage: UIImage?
|
|
private var highlightedImage: UIImage?
|
|
|
|
private var blurredBackgroundNode: NavigationBackgroundNode?
|
|
private var gradientBackgroundNode: GradientBackgroundNode.CloneNode?
|
|
private let backgroundNode: ASImageNode
|
|
|
|
var action: (() -> Void)?
|
|
var cancelAction: (() -> Void)?
|
|
|
|
init(presentationData: PresentationData, background: PasscodeBackground, title: String, subtitle: String) {
|
|
self.presentationData = presentationData
|
|
self.background = background
|
|
self.title = title
|
|
self.subtitle = subtitle
|
|
|
|
if let background = background as? CustomPasscodeBackground {
|
|
let blurredBackgroundColor = (background.inverted ? UIColor(rgb: 0xffffff, alpha: 0.1) : UIColor(rgb: 0x000000, alpha: 0.2), dateFillNeedsBlur(theme: presentationData.theme, wallpaper: presentationData.chatWallpaper))
|
|
let blurredBackgroundNode = NavigationBackgroundNode(color: blurredBackgroundColor.0, enableBlur: blurredBackgroundColor.1)
|
|
self.blurredBackgroundNode = blurredBackgroundNode
|
|
}
|
|
|
|
self.backgroundNode = ASImageNode()
|
|
self.backgroundNode.displaysAsynchronously = false
|
|
self.backgroundNode.displayWithoutProcessing = true
|
|
self.backgroundNode.isUserInteractionEnabled = false
|
|
|
|
super.init()
|
|
|
|
self.accessibilityLabel = title
|
|
self.accessibilityTraits = .keyboardKey
|
|
|
|
if let gradientBackgroundNode = self.gradientBackgroundNode {
|
|
self.addSubnode(gradientBackgroundNode)
|
|
}
|
|
if let blurredBackgroundNode = self.blurredBackgroundNode {
|
|
self.addSubnode(blurredBackgroundNode)
|
|
}
|
|
self.addSubnode(self.backgroundNode)
|
|
|
|
self.highligthedChanged = { [weak self] highlighted in
|
|
if let strongSelf = self {
|
|
strongSelf.updateState(highlighted: highlighted)
|
|
}
|
|
}
|
|
|
|
self.addTarget(self, action: #selector(self.nop), forControlEvents: .touchUpInside)
|
|
}
|
|
|
|
@objc private func nop() {
|
|
}
|
|
|
|
override var frame: CGRect {
|
|
get {
|
|
return super.frame
|
|
}
|
|
set {
|
|
super.frame = newValue
|
|
self.updateGraphics()
|
|
}
|
|
}
|
|
|
|
func updateBackground(_ presentationData: PresentationData, _ background: PasscodeBackground) {
|
|
self.presentationData = presentationData
|
|
self.background = background
|
|
self.updateGraphics()
|
|
}
|
|
|
|
private func updateGraphics() {
|
|
self.regularImage = generateButtonImage(background: self.background, frame: self.frame, title: self.title, subtitle: self.subtitle, highlighted: false)
|
|
self.highlightedImage = generateButtonImage(background: self.background, frame: self.frame, title: self.title, subtitle: self.subtitle, highlighted: true)
|
|
self.updateState(highlighted: self.isHighlighted)
|
|
|
|
if let gradientBackgroundNode = self.gradientBackgroundNode {
|
|
let containerSize = self.background.size
|
|
let shiftedContentsRect = CGRect(origin: CGPoint(x: self.frame.minX / containerSize.width, y: self.frame.minY / containerSize.height), size: CGSize(width: self.frame.width / containerSize.width, height: self.frame.height / containerSize.height))
|
|
gradientBackgroundNode.layer.contentsRect = shiftedContentsRect
|
|
}
|
|
}
|
|
|
|
private func updateState(highlighted: Bool) {
|
|
let image = highlighted ? self.highlightedImage : self.regularImage
|
|
if self.currentImage !== image {
|
|
let currentContents = self.backgroundNode.layer.contents
|
|
self.backgroundNode.layer.removeAnimation(forKey: "contents")
|
|
if let currentContents = currentContents, let image = image {
|
|
self.backgroundNode.image = image
|
|
self.backgroundNode.layer.animate(from: currentContents as AnyObject, to: image.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, duration: image === self.regularImage ? 0.45 : 0.05)
|
|
} else {
|
|
self.backgroundNode.image = image
|
|
}
|
|
self.currentImage = image
|
|
}
|
|
}
|
|
|
|
override func layout() {
|
|
super.layout()
|
|
|
|
if let gradientBackgroundNode = self.gradientBackgroundNode {
|
|
gradientBackgroundNode.frame = self.bounds
|
|
}
|
|
if let blurredBackgroundNode = self.blurredBackgroundNode {
|
|
blurredBackgroundNode.frame = self.bounds
|
|
blurredBackgroundNode.update(size: blurredBackgroundNode.bounds.size, cornerRadius: blurredBackgroundNode.bounds.height / 2.0, transition: .immediate)
|
|
}
|
|
self.backgroundNode.frame = self.bounds
|
|
}
|
|
|
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
super.touchesBegan(touches, with: event)
|
|
|
|
self.action?()
|
|
}
|
|
|
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
super.touchesEnded(touches, with: event)
|
|
|
|
if let touchPosition = touches.first?.location(in: self.view), !self.view.bounds.contains(touchPosition) {
|
|
self.cancelAction?()
|
|
}
|
|
}
|
|
|
|
override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
|
|
super.touchesCancelled(touches, with: event)
|
|
|
|
self.cancelAction?()
|
|
}
|
|
}
|
|
|
|
private let buttonsData = [
|
|
("1", " "),
|
|
("2", "A B C"),
|
|
("3", "D E F"),
|
|
("4", "G H I"),
|
|
("5", "J K L"),
|
|
("6", "M N O"),
|
|
("7", "P Q R S"),
|
|
("8", "T U V"),
|
|
("9", "W X Y Z"),
|
|
("0", "")
|
|
]
|
|
|
|
final class PasscodeEntryKeyboardNode: ASDisplayNode {
|
|
private var presentationData: PresentationData?
|
|
private var background: PasscodeBackground?
|
|
|
|
var charactedEntered: ((String) -> Void)?
|
|
var backspace: (() -> Void)?
|
|
|
|
private func updateButtons() {
|
|
guard let presentationData = self.presentationData, let background = self.background else {
|
|
return
|
|
}
|
|
|
|
if let subnodes = self.subnodes, !subnodes.isEmpty {
|
|
for case let button as PasscodeEntryButtonNode in subnodes {
|
|
button.updateBackground(presentationData, background)
|
|
}
|
|
} else {
|
|
for (title, subtitle) in buttonsData {
|
|
let buttonNode = PasscodeEntryButtonNode(presentationData: presentationData, background: background, title: title, subtitle: subtitle)
|
|
buttonNode.action = { [weak self] in
|
|
self?.charactedEntered?(title)
|
|
}
|
|
buttonNode.cancelAction = { [weak self] in
|
|
self?.backspace?()
|
|
}
|
|
self.addSubnode(buttonNode)
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateBackground(_ presentationData: PresentationData, _ background: PasscodeBackground) {
|
|
self.presentationData = presentationData
|
|
self.background = background
|
|
self.updateButtons()
|
|
}
|
|
|
|
func animateIn() {
|
|
if let subnodes = self.subnodes {
|
|
for i in 0 ..< subnodes.count {
|
|
let subnode = subnodes[i]
|
|
var delay: Double = 0.001
|
|
if i / 3 == 1 {
|
|
delay = 0.05
|
|
}
|
|
else if i / 3 == 2 {
|
|
delay = 0.1
|
|
}
|
|
else if i / 3 == 3 {
|
|
delay = 0.15
|
|
}
|
|
subnode.layer.animateScale(from: 0.0001, to: 1.0, duration: 0.25, delay: delay, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue)
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateLayout(layout: PasscodeLayout, transition: ContainedViewLayoutTransition) -> (CGRect, CGSize) {
|
|
let origin: CGPoint
|
|
let buttonSize: CGFloat
|
|
let horizontalSecond: CGFloat
|
|
let horizontalThird: CGFloat
|
|
let verticalSecond: CGFloat
|
|
let verticalThird: CGFloat
|
|
let verticalFourth: CGFloat
|
|
let keyboardSize: CGSize
|
|
|
|
if layout.layout.orientation == .landscape && layout.layout.deviceMetrics.type != .tablet {
|
|
let horizontalSpacing: CGFloat = 20.0
|
|
let verticalSpacing: CGFloat = 12.0
|
|
buttonSize = 65.0
|
|
keyboardSize = CGSize(width: buttonSize * 3.0 + horizontalSpacing * 2.0, height: buttonSize * 4.0 + verticalSpacing * 3.0)
|
|
horizontalSecond = buttonSize + horizontalSpacing
|
|
horizontalThird = buttonSize * 2.0 + horizontalSpacing * 2.0
|
|
verticalSecond = buttonSize + verticalSpacing
|
|
verticalThird = buttonSize * 2.0 + verticalSpacing * 2.0
|
|
verticalFourth = buttonSize * 3.0 + verticalSpacing * 3.0
|
|
origin = CGPoint(x: floor(layout.layout.size.width / 2.0 + (layout.layout.size.width / 2.0 - keyboardSize.width) / 2.0) - layout.layout.safeInsets.right, y: floor((layout.layout.size.height - keyboardSize.height) / 2.0))
|
|
} else {
|
|
origin = CGPoint(x: floor((layout.layout.size.width - layout.keyboard.size.width) / 2.0), y: layout.keyboard.topOffset)
|
|
buttonSize = layout.keyboard.buttonSize
|
|
horizontalSecond = layout.keyboard.horizontalSecond
|
|
horizontalThird = layout.keyboard.horizontalThird
|
|
verticalSecond = layout.keyboard.verticalSecond
|
|
verticalThird = layout.keyboard.verticalThird
|
|
verticalFourth = layout.keyboard.verticalFourth
|
|
keyboardSize = layout.keyboard.size
|
|
}
|
|
|
|
if let subnodes = self.subnodes {
|
|
for i in 0 ..< subnodes.count {
|
|
var origin = origin
|
|
if i % 3 == 0 {
|
|
origin.x += 0.0
|
|
} else if (i % 3 == 1) {
|
|
origin.x += horizontalSecond
|
|
}
|
|
else {
|
|
origin.x += horizontalThird
|
|
}
|
|
|
|
if i / 3 == 0 {
|
|
origin.y += 0.0
|
|
}
|
|
else if i / 3 == 1 {
|
|
origin.y += verticalSecond
|
|
}
|
|
else if i / 3 == 2 {
|
|
origin.y += verticalThird
|
|
}
|
|
else if i / 3 == 3 {
|
|
origin.x += horizontalSecond
|
|
origin.y += verticalFourth
|
|
}
|
|
transition.updateFrame(node: subnodes[i], frame: CGRect(origin: origin, size: CGSize(width: buttonSize, height: buttonSize)))
|
|
}
|
|
}
|
|
return (CGRect(origin: origin, size: keyboardSize), CGSize(width: buttonSize, height: buttonSize))
|
|
}
|
|
|
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
let result = super.hitTest(point, with: event)
|
|
if let result = result, result.isDescendant(of: self.view) {
|
|
return result
|
|
}
|
|
return nil
|
|
}
|
|
}
|