mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
251 lines
12 KiB
Swift
251 lines
12 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import AppBundle
|
|
import AccountContext
|
|
import TelegramPresentationData
|
|
import AsyncDisplayKit
|
|
import Display
|
|
import Postbox
|
|
import TelegramCore
|
|
import AnimationUI
|
|
import SwiftSignalKit
|
|
import OverlayStatusController
|
|
import PasscodeUI
|
|
|
|
public enum WalletPasscodeMode {
|
|
case setup
|
|
case authorizeTransfer(WalletInfo, String, Int64, String)
|
|
}
|
|
|
|
public final class WalletPasscodeScreen: ViewController {
|
|
private let context: AccountContext
|
|
private let tonContext: TonContext
|
|
private var presentationData: PresentationData
|
|
private let mode: WalletPasscodeMode
|
|
|
|
public init(context: AccountContext, tonContext: TonContext, mode: WalletPasscodeMode) {
|
|
self.context = context
|
|
self.tonContext = tonContext
|
|
self.mode = mode
|
|
|
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
|
|
let defaultNavigationPresentationData = NavigationBarPresentationData(presentationTheme: self.presentationData.theme, presentationStrings: self.presentationData.strings)
|
|
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultNavigationPresentationData.theme.buttonColor, disabledButtonColor: defaultNavigationPresentationData.theme.disabledButtonColor, primaryTextColor: defaultNavigationPresentationData.theme.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: defaultNavigationPresentationData.theme.badgeBackgroundColor, badgeStrokeColor: defaultNavigationPresentationData.theme.badgeStrokeColor, badgeTextColor: defaultNavigationPresentationData.theme.badgeTextColor)
|
|
|
|
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: defaultNavigationPresentationData.strings))
|
|
|
|
self.navigationPresentation = .modalInLargeLayout
|
|
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
|
|
self.navigationBar?.intrinsicCanTransitionInline = false
|
|
|
|
self.navigationItem.setLeftBarButton(UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.backPressed)), animated: false)
|
|
|
|
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
|
}
|
|
|
|
required init(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
@objc private func backPressed() {
|
|
self.view.endEditing(true)
|
|
self.dismiss()
|
|
}
|
|
|
|
override public func loadDisplayNode() {
|
|
self.displayNode = WalletPasscodeScreenNode(account: self.context.account, presentationData: self.presentationData, mode: self.mode, proceed: { [weak self] in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
switch strongSelf.mode {
|
|
case .setup:
|
|
break
|
|
case let .authorizeTransfer(walletInfo, address, amount, comment):
|
|
if let navigationController = strongSelf.navigationController as? NavigationController {
|
|
var controllers = navigationController.viewControllers
|
|
controllers = controllers.filter { controller in
|
|
if controller is WalletSplashScreen {
|
|
return false
|
|
}
|
|
if controller is WalletSendScreen {
|
|
return false
|
|
}
|
|
if controller is WalletPasscodeScreen {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
controllers.append(WalletSplashScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, mode: .sending(walletInfo, address, amount, comment)))
|
|
strongSelf.view.endEditing(true)
|
|
navigationController.setViewControllers(controllers, animated: true)
|
|
}
|
|
}
|
|
}, requestBiometrics: {
|
|
|
|
})
|
|
|
|
self.displayNodeDidLoad()
|
|
}
|
|
|
|
override public func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
|
|
(self.displayNode as! WalletPasscodeScreenNode).activateInput()
|
|
}
|
|
|
|
override public func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
self.view.disablesInteractiveTransitionGestureRecognizer = true
|
|
|
|
(self.displayNode as! WalletPasscodeScreenNode).activateInput()
|
|
}
|
|
|
|
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
|
super.containerLayoutUpdated(layout, transition: transition)
|
|
|
|
(self.displayNode as! WalletPasscodeScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationHeight, transition: transition)
|
|
}
|
|
}
|
|
|
|
private final class WalletPasscodeScreenNode: ViewControllerTracingNode {
|
|
private var presentationData: PresentationData
|
|
private let mode: WalletPasscodeMode
|
|
private let requestBiometrics: () -> Void
|
|
|
|
private let iconNode: ASImageNode
|
|
private let animationNode: AnimatedStickerNode
|
|
private let titleNode: ImmediateTextNode
|
|
private let biometricsActionTitleNode: ImmediateTextNode
|
|
private let biometricsActionButtonNode: HighlightTrackingButtonNode
|
|
private let inputFieldNode: PasscodeEntryInputFieldNode
|
|
|
|
private let hapticFeedback = HapticFeedback()
|
|
|
|
init(account: Account, presentationData: PresentationData, mode: WalletPasscodeMode, proceed: @escaping () -> Void, requestBiometrics: @escaping () -> Void) {
|
|
self.presentationData = presentationData
|
|
self.mode = mode
|
|
self.requestBiometrics = requestBiometrics
|
|
|
|
self.iconNode = ASImageNode()
|
|
self.iconNode.displayWithoutProcessing = true
|
|
self.iconNode.displaysAsynchronously = false
|
|
|
|
self.animationNode = AnimatedStickerNode()
|
|
|
|
let title: String
|
|
let biometricsActionText: String
|
|
|
|
title = "Enter Passcode"
|
|
biometricsActionText = "Use Face ID"
|
|
|
|
self.iconNode.image = UIImage(bundleImageName: "Settings/Wallet/PasscodeIcon")
|
|
|
|
self.titleNode = ImmediateTextNode()
|
|
self.titleNode.displaysAsynchronously = false
|
|
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(32.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
|
self.titleNode.maximumNumberOfLines = 0
|
|
self.titleNode.textAlignment = .center
|
|
|
|
self.biometricsActionTitleNode = ImmediateTextNode()
|
|
self.biometricsActionTitleNode.displaysAsynchronously = false
|
|
self.biometricsActionTitleNode.attributedText = NSAttributedString(string: biometricsActionText, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemAccentColor, paragraphAlignment: .center)
|
|
self.biometricsActionTitleNode.textAlignment = .center
|
|
|
|
self.biometricsActionButtonNode = HighlightTrackingButtonNode()
|
|
|
|
self.inputFieldNode = PasscodeEntryInputFieldNode(color: self.presentationData.theme.list.itemPrimaryTextColor, accentColor: self.presentationData.theme.list.itemAccentColor, fieldType: .digits4, keyboardAppearance: self.presentationData.theme.rootController.keyboardColor.keyboardAppearance)
|
|
|
|
super.init()
|
|
|
|
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
|
|
|
self.addSubnode(self.iconNode)
|
|
self.addSubnode(self.animationNode)
|
|
self.addSubnode(self.titleNode)
|
|
self.addSubnode(self.biometricsActionTitleNode)
|
|
self.addSubnode(self.biometricsActionButtonNode)
|
|
self.addSubnode(self.inputFieldNode)
|
|
|
|
self.biometricsActionButtonNode.highligthedChanged = { [weak self] highlighted in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
if highlighted {
|
|
strongSelf.biometricsActionTitleNode.layer.removeAnimation(forKey: "opacity")
|
|
strongSelf.biometricsActionTitleNode.alpha = 0.4
|
|
} else {
|
|
strongSelf.biometricsActionTitleNode.alpha = 1.0
|
|
strongSelf.biometricsActionTitleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
|
|
}
|
|
}
|
|
|
|
self.biometricsActionButtonNode.addTarget(self, action: #selector(self.biometricsActionPressed), forControlEvents: .touchUpInside)
|
|
|
|
self.inputFieldNode.complete = { [weak self] passcode in
|
|
if passcode == "1111" {
|
|
proceed()
|
|
} else {
|
|
self?.animateError()
|
|
}
|
|
}
|
|
}
|
|
|
|
@objc private func biometricsActionPressed() {
|
|
self.requestBiometrics()
|
|
}
|
|
|
|
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
let sideInset: CGFloat = 32.0
|
|
let buttonSideInset: CGFloat = 48.0
|
|
let iconSpacing: CGFloat = 21.0
|
|
let titleSpacing: CGFloat = 60.0
|
|
let biometricsSpacing: CGFloat = 44.0
|
|
let buttonHeight: CGFloat = 50.0
|
|
let inputFieldHeight: CGFloat = 34.0
|
|
|
|
let iconSize = self.iconNode.image?.size ?? CGSize(width: 140.0, height: 140.0)
|
|
var iconOffset = CGPoint()
|
|
|
|
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
|
|
let biometricsActionSize = self.biometricsActionTitleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
|
|
|
|
let insets = layout.insets(options: [.input])
|
|
let contentHeight = iconSize.height + iconSpacing + titleSize.height + titleSpacing + inputFieldHeight
|
|
let contentVerticalOrigin = floor((layout.size.height - contentHeight - iconSize.height / 2.0 - insets.bottom) / 2.0)
|
|
|
|
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize).offsetBy(dx: iconOffset.x, dy: iconOffset.y)
|
|
transition.updateFrameAdditive(node: self.iconNode, frame: iconFrame)
|
|
self.animationNode.updateLayout(size: iconFrame.size)
|
|
transition.updateFrameAdditive(node: self.animationNode, frame: iconFrame)
|
|
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleSize)
|
|
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
|
|
|
|
let passcodeLayout = PasscodeLayout(layout: layout, titleOffset: 0.0, subtitleOffset: 0.0, inputFieldOffset: titleFrame.maxY + titleSpacing)
|
|
let inputFieldFrame = self.inputFieldNode.updateLayout(layout: passcodeLayout, transition: transition)
|
|
transition.updateFrame(node: self.inputFieldNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
|
|
|
let minimalBottomInset: CGFloat = 60.0
|
|
let bottomInset = layout.intrinsicInsets.bottom + max(minimalBottomInset, biometricsActionSize.height + biometricsSpacing * 2.0)
|
|
|
|
if !biometricsActionSize.width.isZero {
|
|
let biometricsActionFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - biometricsActionSize.width) / 2.0), y: inputFieldFrame.maxY + floor((layout.size.height - insets.bottom - inputFieldFrame.maxY - biometricsActionSize.height) / 2.0)), size: biometricsActionSize)
|
|
transition.updateFrameAdditive(node: self.biometricsActionTitleNode, frame: biometricsActionFrame)
|
|
transition.updateFrame(node: self.biometricsActionButtonNode, frame: biometricsActionFrame.insetBy(dx: -10.0, dy: -10.0))
|
|
}
|
|
}
|
|
|
|
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()
|
|
}
|
|
}
|