Swiftgram/submodules/WalletUI/Sources/WalletWordDisplayScreen.swift
2019-09-20 19:17:04 +04:00

337 lines
18 KiB
Swift

import Foundation
import UIKit
import AppBundle
import AccountContext
import TelegramPresentationData
import AsyncDisplayKit
import Display
import Postbox
import TelegramCore
import SolidRoundedButtonNode
import UndoUI
import AlertUI
public final class WalletWordDisplayScreen: ViewController {
private let context: AccountContext
private let tonContext: TonContext
private var presentationData: PresentationData
private let walletInfo: WalletInfo
private let wordList: [String]
private let startTime: Double
public init(context: AccountContext, tonContext: TonContext, walletInfo: WalletInfo, wordList: [String]) {
self.context = context
self.tonContext = tonContext
self.walletInfo = walletInfo
self.wordList = wordList
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)
self.startTime = Date().timeIntervalSince1970
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.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.dismiss()
}
override public func loadDisplayNode() {
self.displayNode = WalletWordDisplayScreenNode(presentationData: self.presentationData, wordList: self.wordList, action: { [weak self] in
guard let strongSelf = self else {
return
}
let deltaTime = Date().timeIntervalSince1970 - strongSelf.startTime
let minimalTimeout: Double
#if DEBUG
minimalTimeout = 1.0
#else
minimalTimeout = 60.0
#endif
if deltaTime < minimalTimeout {
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: strongSelf.presentationData.theme), title: "Sure Done?", text: "You didn't have enough time to write those words down.", actions: [TextAlertAction(type: .defaultAction, title: "OK, Sorry", action: {
guard let strongSelf = self else {
return
}
if let path = getAppBundle().path(forResource: "thumbsup", ofType: "tgs") {
strongSelf.present(UndoOverlayController(context: strongSelf.context, content: UndoOverlayContent.emoji(account: strongSelf.context.account, path: path, text: "Apologies Accepted"), elevatedLayout: false, animateInAsReplacement: false, action: { _ in }), in: .current)
}
})]), in: .window(.root))
} else {
strongSelf.push(WalletWordCheckScreen(context: strongSelf.context, tonContext: strongSelf.tonContext, mode: .verify(strongSelf.walletInfo, strongSelf.wordList)))
}
})
self.displayNodeDidLoad()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
(self.displayNode as! WalletWordDisplayScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationHeight, transition: transition)
}
}
private final class WalletWordDisplayScreenNode: ViewControllerTracingNode, UIScrollViewDelegate {
private var presentationData: PresentationData
private let wordList: [String]
private let action: () -> Void
private let navigationBackgroundNode: ASDisplayNode
private let navigationSeparatorNode: ASDisplayNode
private let navigationTitleNode: ImmediateTextNode
private let scrollNode: ASScrollNode
private let iconNode: ASImageNode
private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let wordNodes: [(ImmediateTextNode, ImmediateTextNode, ImmediateTextNode)]
private let buttonNode: SolidRoundedButtonNode
private var navigationHeight: CGFloat?
init(presentationData: PresentationData, wordList: [String], action: @escaping () -> Void) {
self.presentationData = presentationData
self.wordList = wordList
self.action = action
self.navigationBackgroundNode = ASDisplayNode()
self.navigationBackgroundNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.backgroundColor
self.navigationBackgroundNode.alpha = 0.0
self.navigationSeparatorNode = ASDisplayNode()
self.navigationSeparatorNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
self.scrollNode = ASScrollNode()
self.iconNode = ASImageNode()
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
let title: String = "24 Secret Words"
let text: String = "Write down these 24 words in the correct order and store them in a secret place.\n\nUse these secret words to restore access to your wallet if you lose your passcode or Telegram account."
let buttonText: String = "Done"
self.iconNode.image = UIImage(bundleImageName: "Settings/Wallet/WordsDisplayIcon")
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.navigationTitleNode = ImmediateTextNode()
self.navigationTitleNode.displaysAsynchronously = false
self.navigationTitleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
self.navigationTitleNode.maximumNumberOfLines = 0
self.navigationTitleNode.textAlignment = .center
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(16.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
self.textNode.maximumNumberOfLines = 0
self.textNode.lineSpacing = 0.1
self.textNode.textAlignment = .center
var wordNodes: [(ImmediateTextNode, ImmediateTextNode, ImmediateTextNode)] = []
for i in 0 ..< wordList.count {
let indexNode = ImmediateTextNode()
indexNode.displaysAsynchronously = false
indexNode.attributedText = NSAttributedString(string: "\(i + 1)", font: Font.regular(18.0), textColor: self.presentationData.theme.list.itemSecondaryTextColor)
indexNode.maximumNumberOfLines = 1
indexNode.textAlignment = .left
let indexDotNode = ImmediateTextNode()
indexDotNode.displaysAsynchronously = false
indexDotNode.attributedText = NSAttributedString(string: ".", font: Font.regular(18.0), textColor: self.presentationData.theme.list.itemSecondaryTextColor)
indexDotNode.maximumNumberOfLines = 1
indexDotNode.textAlignment = .left
let wordNode = ImmediateTextNode()
wordNode.displaysAsynchronously = false
wordNode.attributedText = NSAttributedString(string: wordList[i], font: Font.semibold(18.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
wordNode.maximumNumberOfLines = 1
wordNode.textAlignment = .left
wordNodes.append((indexNode, indexDotNode, wordNode))
}
self.wordNodes = wordNodes
self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 50.0, cornerRadius: 10.0, gloss: false)
super.init()
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.addSubnode(self.scrollNode)
self.scrollNode.addSubnode(self.iconNode)
self.scrollNode.addSubnode(self.titleNode)
self.scrollNode.addSubnode(self.textNode)
self.scrollNode.addSubnode(self.buttonNode)
for (indexNode, indexDotNode, wordNode) in self.wordNodes {
self.scrollNode.addSubnode(indexNode)
self.scrollNode.addSubnode(indexDotNode)
self.scrollNode.addSubnode(wordNode)
}
self.navigationBackgroundNode.addSubnode(self.navigationSeparatorNode)
self.navigationBackgroundNode.addSubnode(self.navigationTitleNode)
self.addSubnode(self.navigationBackgroundNode)
self.buttonNode.pressed = {
action()
}
}
override func didLoad() {
super.didLoad()
self.scrollNode.view.alwaysBounceVertical = false
self.scrollNode.view.showsVerticalScrollIndicator = false
self.scrollNode.view.showsHorizontalScrollIndicator = false
if #available(iOSApplicationExtension 11.0, iOS 11.0, *) {
self.scrollNode.view.contentInsetAdjustmentBehavior = .never
}
self.scrollNode.view.delegate = self
#if DEBUG
self.textNode.view.addGestureRecognizer(UILongPressGestureRecognizer(target: self, action: #selector(self.textLongPressGesture(_:))))
#endif
}
@objc func textLongPressGesture(_ recognizer: UILongPressGestureRecognizer) {
if case .began = recognizer.state {
UIPasteboard.general.string = self.wordList.joined(separator: "\n")
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let navigationHeight = self.navigationHeight ?? 0.0
let alpha: CGFloat = scrollView.contentOffset.y >= (self.titleNode.frame.maxY - navigationHeight) ? 1.0 : 0.0
if self.navigationBackgroundNode.alpha != alpha {
let transition: ContainedViewLayoutTransition = .animated(duration: 0.12, curve: .easeInOut)
transition.updateAlpha(node: self.navigationBackgroundNode, alpha: alpha, beginWithCurrentState: true)
}
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.navigationHeight = navigationHeight
let sideInset: CGFloat = 32.0
let buttonSideInset: CGFloat = 48.0
let iconSpacing: CGFloat = 5.0
let titleSpacing: CGFloat = 19.0
let textSpacing: CGFloat = 37.0
let buttonHeight: CGFloat = 50.0
let buttonSpacing: CGFloat = 45.0
let wordSpacing: CGFloat = 12.0
let indexSpacing: CGFloat = 4.0
transition.updateFrame(node: self.navigationBackgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: navigationHeight)))
transition.updateFrame(node: self.navigationSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel)))
transition.updateFrame(node: self.scrollNode, frame: CGRect(origin: CGPoint(), size: layout.size))
let iconSize = self.iconNode.image?.size ?? CGSize(width: 50.0, height: 50.0)
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let navigationTitleSize = self.navigationTitleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
var contentHeight: CGFloat = 0.0
let contentVerticalOrigin = navigationHeight + 10.0
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: contentVerticalOrigin), size: iconSize)
transition.updateFrameAdditive(node: self.iconNode, 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 textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: textSize)
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
transition.updateFrameAdditive(node: self.navigationTitleNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - navigationTitleSize.width) / 2.0), y: navigationHeight - 44.0 + floor((44.0 - navigationTitleSize.height) / 2.0)), size: navigationTitleSize))
contentHeight = textFrame.maxY + textSpacing
let rowWidth = layout.size.width - buttonSideInset * 2.0
let rowCount = self.wordNodes.count / 2
let indexWidth: CGFloat = 16.0
var wordSizes: [(CGSize, CGSize)] = []
var columnIndexWidth: [CGFloat] = [0.0, 0.0]
var columnWordWidth: [CGFloat] = [0.0, 0.0]
var dotSize: CGSize = CGSize()
for i in 0 ..< self.wordNodes.count {
let indexSize = self.wordNodes[i].0.updateLayout(CGSize(width: 200.0, height: 100.0))
dotSize = self.wordNodes[i].1.updateLayout(CGSize(width: 200.0, height: 100.0))
let wordSize = self.wordNodes[i].2.updateLayout(CGSize(width: 200.0, height: 100.0))
wordSizes.append((indexSize, wordSize))
let column = i / rowCount
columnIndexWidth[column] = max(columnIndexWidth[column], indexSize.width)
columnWordWidth[column] = max(columnWordWidth[column], wordSize.width)
}
for column in 0 ..< 2 {
var columnHeight: CGFloat = 0.0
for i in 0 ..< self.wordNodes.count {
if !columnHeight.isZero {
columnHeight += wordSpacing
}
if i / rowCount != column {
continue
}
let horizontalOrigin: CGFloat
let verticalOrigin: CGFloat = contentHeight + columnHeight
if column == 0 {
horizontalOrigin = buttonSideInset + columnIndexWidth[column]
} else {
horizontalOrigin = layout.size.width - buttonSideInset - columnWordWidth[column] - indexSpacing
}
let indexSize = self.wordNodes[i].0.updateLayout(CGSize(width: 200.0, height: 100.0))
let wordSize = self.wordNodes[i].2.updateLayout(CGSize(width: 200.0, height: 100.0))
transition.updateFrameAdditive(node: self.wordNodes[i].0, frame: CGRect(origin: CGPoint(x: horizontalOrigin - indexSize.width - dotSize.width, y: verticalOrigin), size: indexSize))
transition.updateFrameAdditive(node: self.wordNodes[i].1, frame: CGRect(origin: CGPoint(x: horizontalOrigin - dotSize.width, y: verticalOrigin), size: indexSize))
transition.updateFrameAdditive(node: self.wordNodes[i].2, frame: CGRect(origin: CGPoint(x: horizontalOrigin + indexSpacing, y: verticalOrigin), size: wordSize))
columnHeight += wordSize.height
}
if column == 1 {
contentHeight += columnHeight
}
}
let minimalFullscreenBottomInset: CGFloat = 74.0
let minimalScrollBottomInset: CGFloat = 30.0
let fullscreenBottomInset = layout.intrinsicInsets.bottom + minimalFullscreenBottomInset
let scrollBottomInset = layout.intrinsicInsets.bottom + minimalScrollBottomInset
let buttonWidth = layout.size.width - buttonSideInset * 2.0
let buttonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: max(contentHeight + buttonSpacing, layout.size.height - scrollBottomInset - buttonHeight)), size: CGSize(width: buttonWidth, height: buttonHeight))
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
self.scrollNode.view.contentSize = CGSize(width: layout.size.width, height: max(layout.size.height, buttonFrame.maxY + scrollBottomInset))
}
}