Update QR auth UI

This commit is contained in:
Ali
2019-12-07 00:51:43 +04:00
parent 0a20d69a3d
commit 88fe8966e8
18 changed files with 4221 additions and 4041 deletions

View File

@@ -6,161 +6,279 @@ import Display
import SolidRoundedButtonNode
import SwiftSignalKit
import OverlayStatusController
import AnimatedStickerNode
import TelegramPresentationData
import TelegramCore
import AnimationUI
import AccountContext
import TelegramPresentationData
import PresentationDataUtils
import TelegramCore
import Markdown
final class AuthTransferConfirmationNode: ASDisplayNode {
public final class AuthDataTransferSplashScreen: ViewController {
private let context: AccountContext
private let activeSessionsContext: ActiveSessionsContext
private var presentationData: PresentationData
private let tokenInfo: AuthTransferTokenInfo
private let containerNode: ASDisplayNode
private let backgroundNode: ASImageNode
private let iconNode: ASImageNode
private let titleNode: ImmediateTextNode
private let appNameNode: ImmediateTextNode
private let locationInfoNode: ImmediateTextNode
private let acceptButtonNode: SolidRoundedButtonNode
private let cancelButtonNode: SolidRoundedButtonNode
private var validLayout: (ContainerViewLayout, CGFloat)?
init(context: AccountContext, presentationData: PresentationData, tokenInfo: AuthTransferTokenInfo, accept: @escaping () -> Void, cancel: @escaping () -> Void) {
public init(context: AccountContext, activeSessionsContext: ActiveSessionsContext) {
self.context = context
self.activeSessionsContext = activeSessionsContext
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
let defaultTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme)
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))
self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style
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")
}
deinit {
}
override public func loadDisplayNode() {
self.displayNode = AuthDataTransferSplashScreenNode(context: self.context, presentationData: self.presentationData, action: { [weak self] in
guard let strongSelf = self else {
return
}
(strongSelf.navigationController as? NavigationController)?.replaceController(strongSelf, with: AuthTransferScanScreen(context: strongSelf.context, activeSessionsContext: strongSelf.activeSessionsContext), animated: true)
})
self.displayNodeDidLoad()
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
(self.displayNode as! AuthDataTransferSplashScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationHeight, transition: transition)
}
}
private final class AuthDataTransferSplashScreenNode: ViewControllerTracingNode {
private var presentationData: PresentationData
private var animationSize: CGSize = CGSize()
private var animationOffset: CGPoint = CGPoint()
private let animationNode: AnimationNode
private let titleNode: ImmediateTextNode
private let badgeBackgroundNodes: [ASImageNode]
private let badgeTextNodes: [ImmediateTextNode]
private let textNodes: [ImmediateTextNode]
let buttonNode: SolidRoundedButtonNode
var inProgress: Bool = false {
didSet {
self.buttonNode.isUserInteractionEnabled = !self.inProgress
self.buttonNode.alpha = self.inProgress ? 0.6 : 1.0
}
}
private var validLayout: ContainerViewLayout?
init(context: AccountContext, presentationData: PresentationData, action: @escaping () -> Void) {
self.presentationData = presentationData
self.tokenInfo = tokenInfo
self.containerNode = ASDisplayNode()
self.animationNode = AnimationNode(animation: "anim_qr", colors: nil, scale: UIScreenScale)
self.backgroundNode = ASImageNode()
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 24.0, color: self.presentationData.theme.list.plainBackgroundColor)
let buttonText: String
self.iconNode = ASImageNode()
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
self.iconNode.image = UIImage(bundleImageName: "Settings/TransferAuthLaptop")
let badgeFont = Font.with(size: 14.0, design: .round, traits: [.bold])
let textFont = Font.regular(18.0)
let textColor = self.presentationData.theme.list.itemPrimaryTextColor
var badgeBackgroundNodes: [ASImageNode] = []
var badgeTextNodes: [ImmediateTextNode] = []
var textNodes: [ImmediateTextNode] = []
let badgeBackground = generateFilledCircleImage(diameter: 20.0, color: self.presentationData.theme.list.itemCheckColors.fillColor)
for i in 0 ..< 3 {
let badgeBackgroundNode = ASImageNode()
badgeBackgroundNode.displaysAsynchronously = false
badgeBackgroundNode.displayWithoutProcessing = true
badgeBackgroundNode.image = badgeBackground
badgeBackgroundNodes.append(badgeBackgroundNode)
let badgeTextNode = ImmediateTextNode()
badgeTextNode.displaysAsynchronously = false
badgeTextNode.attributedText = NSAttributedString(string: "\(i + 1)", font: badgeFont, textColor: self.presentationData.theme.list.itemCheckColors.foregroundColor)
badgeTextNode.maximumNumberOfLines = 0
badgeTextNode.lineSpacing = 0.1
badgeTextNodes.append(badgeTextNode)
let string: String
switch i {
case 0:
string = self.presentationData.strings.AuthSessions_AddDeviceIntro_Text1
case 1:
string = self.presentationData.strings.AuthSessions_AddDeviceIntro_Text2
default:
string = self.presentationData.strings.AuthSessions_AddDeviceIntro_Text3
}
let body = MarkdownAttributeSet(font: textFont, textColor: textColor)
let link = MarkdownAttributeSet(font: textFont, textColor: self.presentationData.theme.list.itemAccentColor, additionalAttributes: ["URL": true as NSNumber])
let text = parseMarkdownIntoAttributedString(string, attributes: MarkdownAttributes(body: body, bold: body, link: link, linkAttribute: { _ in
return nil
}))
let textNode = ImmediateTextNode()
textNode.displaysAsynchronously = false
textNode.attributedText = text
textNode.maximumNumberOfLines = 0
textNode.lineSpacing = 0.1
textNodes.append(textNode)
}
self.badgeBackgroundNodes = badgeBackgroundNodes
self.badgeTextNodes = badgeTextNodes
self.textNodes = textNodes
buttonText = self.presentationData.strings.AuthSessions_AddDeviceIntro_Action
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.AuthSessions_AddDeviceIntro_Title, font: Font.bold(28.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
self.titleNode.maximumNumberOfLines = 0
self.titleNode.textAlignment = .center
self.titleNode.maximumNumberOfLines = 2
self.appNameNode = ImmediateTextNode()
self.appNameNode.textAlignment = .center
self.appNameNode.maximumNumberOfLines = 2
self.locationInfoNode = ImmediateTextNode()
self.locationInfoNode.textAlignment = .center
self.locationInfoNode.maximumNumberOfLines = 0
self.acceptButtonNode = SolidRoundedButtonNode(title: presentationData.strings.AuthSessions_AddDevice_ConfirmDevice, icon: nil, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemDestructiveColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 10.0, gloss: false)
self.cancelButtonNode = SolidRoundedButtonNode(title: self.presentationData.strings.Common_Cancel, icon: nil, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 10.0, gloss: false)
self.buttonNode = SolidRoundedButtonNode(title: buttonText, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 10.0, gloss: false)
self.buttonNode.isHidden = buttonText.isEmpty
super.init()
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.backgroundNode)
self.containerNode.addSubnode(self.iconNode)
self.containerNode.addSubnode(self.titleNode)
self.containerNode.addSubnode(self.appNameNode)
self.containerNode.addSubnode(self.locationInfoNode)
self.containerNode.addSubnode(self.acceptButtonNode)
self.containerNode.addSubnode(self.cancelButtonNode)
let titleFont = Font.bold(24.0)
let subtitleFont = Font.regular(16.0)
let textColor = self.presentationData.theme.list.itemPrimaryTextColor
let seccondaryTextColor = self.presentationData.theme.list.itemSecondaryTextColor
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.titleNode.attributedText = NSAttributedString(string: "\(tokenInfo.appName)", font: titleFont, textColor: textColor)
self.addSubnode(self.animationNode)
self.addSubnode(self.titleNode)
self.appNameNode.attributedText = NSAttributedString(string: "\(tokenInfo.deviceModel), \(tokenInfo.platform) \(tokenInfo.systemVersion)", font: subtitleFont, textColor: seccondaryTextColor)
self.badgeBackgroundNodes.forEach(self.addSubnode)
self.badgeTextNodes.forEach(self.addSubnode)
self.textNodes.forEach(self.addSubnode)
self.locationInfoNode.attributedText = NSAttributedString(string: "\(tokenInfo.region)\nIP: \(tokenInfo.ip)", font: subtitleFont, textColor: seccondaryTextColor)
self.addSubnode(self.buttonNode)
self.acceptButtonNode.pressed = { [weak self] in
accept()
self.buttonNode.pressed = {
action()
}
self.cancelButtonNode.pressed = {
cancel()
for textNode in self.textNodes {
textNode.linkHighlightColor = self.presentationData.theme.list.itemAccentColor.withAlphaComponent(0.5)
textNode.highlightAttributeAction = { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
return NSAttributedString.Key(rawValue: "URL")
} else {
return nil
}
}
textNode.tapAttributeAction = { attributes in
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://telegram.org/desktop", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
}
}
}
}
override func didLoad() {
super.didLoad()
}
func animateIn() {
self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: self.containerNode.bounds.height), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
func animateOut(completion: @escaping () -> Void) {
self.containerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: self.containerNode.bounds.height), duration: 0.3, removeOnCompletion: false, additive: true, completion: { _ in
completion()
})
}
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
var insets = layout.insets(options: [])
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
let firstTime = self.validLayout == nil
self.validLayout = layout
let sideInset: CGFloat = 22.0
let buttonSideInset: CGFloat = 16.0
let bottomInset = insets.bottom + 10.0
let buttonWidth = layout.size.width - buttonSideInset * 2.0
let textSideInset: CGFloat = 54.0
let buttonSideInset: CGFloat = 48.0
let titleSpacing: CGFloat = 30.0
let buttonHeight: CGFloat = 50.0
let buttonSpacing: CGFloat = 20.0
let contentButtonSpacing: CGFloat = 35.0
let titleSpacing: CGFloat = 1.0
let locationSpacing: CGFloat = 35.0
let iconSpacing: CGFloat = 35.0
let topInset: CGFloat = 35.0
let buttonSpacing: CGFloat = 10.0
let textSpacing: CGFloat = 26.0
let badgeSize: CGFloat = 20.0
let iconSize = self.iconNode.image?.size ?? CGSize(width: 10.0, height: 1.0)
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: .greatestFiniteMagnitude))
let appNameSize = self.appNameNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: .greatestFiniteMagnitude))
let locationSize = self.locationInfoNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: .greatestFiniteMagnitude))
let animationFitSize = CGSize(width: min(500.0, layout.size.width - sideInset), height: 500.0)
let animationSize = self.animationNode.preferredSize()?.fitted(animationFitSize) ?? animationFitSize
let iconSize: CGSize = animationSize
var iconOffset = CGPoint()
var contentHeight: CGFloat = 0.0
contentHeight += topInset + iconSize.height
contentHeight += iconSpacing + titleSize.height
contentHeight += titleSpacing + appNameSize.height
contentHeight += locationSpacing + locationSize.height
contentHeight += contentButtonSpacing + bottomInset + buttonHeight + buttonSpacing + buttonHeight
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: topInset), size: iconSize)
transition.updateFrame(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.updateFrame(node: self.titleNode, frame: titleFrame)
let appNameFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - appNameSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: appNameSize)
transition.updateFrame(node: self.appNameNode, frame: appNameFrame)
let locationFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - locationSize.width) / 2.0), y: appNameFrame.maxY + locationSpacing), size: locationSize)
transition.updateFrame(node: self.locationInfoNode, frame: locationFrame)
let cancelButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: contentHeight - bottomInset - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight))
transition.updateFrame(node: self.cancelButtonNode, frame: cancelButtonFrame)
self.cancelButtonNode.updateLayout(width: cancelButtonFrame.width, transition: transition)
let acceptButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: cancelButtonFrame.minY - buttonSpacing - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight))
transition.updateFrame(node: self.acceptButtonNode, frame: acceptButtonFrame)
self.acceptButtonNode.updateLayout(width: acceptButtonFrame.width, transition: transition)
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - contentHeight), size: CGSize(width: layout.size.width, height: contentHeight)))
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: contentHeight + 24.0)))
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let result = self.cancelButtonNode.view.hitTest(self.view.convert(point, to: self.cancelButtonNode.view), with: event) {
return result
var badgeTextSizes: [CGSize] = []
var textSizes: [CGSize] = []
var textContentHeight: CGFloat = 0.0
for i in 0 ..< self.badgeTextNodes.count {
let badgeTextSize = self.badgeTextNodes[i].updateLayout(CGSize(width: 100.0, height: .greatestFiniteMagnitude))
badgeTextSizes.append(badgeTextSize)
let textSize = self.textNodes[i].updateLayout(CGSize(width: layout.size.width - sideInset * 2.0 - 40.0, height: .greatestFiniteMagnitude))
textSizes.append(textSize)
if i != 0 {
textContentHeight += textSpacing
}
textContentHeight += textSize.height
}
if let result = self.acceptButtonNode.view.hitTest(self.view.convert(point, to: self.acceptButtonNode.view), with: event) {
return result
var contentHeight = iconSize.height + titleSize.height + titleSpacing + textContentHeight
let bottomInset = layout.intrinsicInsets.bottom + 20.0
let contentTopInset = navigationHeight
let contentBottomInset = bottomInset + buttonHeight + buttonSpacing
let iconSpacing: CGFloat = max(20.0, min(64.0, layout.size.height - contentTopInset - contentBottomInset - contentHeight - 40.0))
contentHeight += iconSpacing
var contentVerticalOrigin = contentTopInset + floor((layout.size.height - contentTopInset - contentBottomInset - contentHeight) / 2.0)
let buttonWidth = layout.size.width - buttonSideInset * 2.0
let buttonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight))
transition.updateFrame(node: self.buttonNode, frame: buttonFrame)
self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition)
var maxContentVerticalOrigin = buttonFrame.minY - 12.0 - contentHeight
contentVerticalOrigin = min(contentVerticalOrigin, maxContentVerticalOrigin)
var contentY = contentVerticalOrigin
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0) + self.animationOffset.x, y: contentY), size: iconSize).offsetBy(dx: iconOffset.x, dy: iconOffset.y)
contentY += iconSize.height + iconSpacing
transition.updateFrameAdditive(node: self.animationNode, frame: iconFrame)
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: contentY), size: titleSize)
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
contentY += titleSize.height + titleSpacing
for i in 0 ..< self.badgeTextNodes.count {
if i != 0 {
contentY += textSpacing
}
let badgeTextSize = badgeTextSizes[i]
let textSize = textSizes[i]
let textFrame = CGRect(origin: CGPoint(x: textSideInset, y: contentY), size: textSize)
transition.updateFrameAdditive(node: self.textNodes[i], frame: textFrame)
let badgeFrame = CGRect(origin: CGPoint(x: sideInset, y: textFrame.minY + floor((textFrame.height - badgeSize) / 2.0)), size: CGSize(width: badgeSize, height: badgeSize))
transition.updateFrameAdditive(node: self.badgeBackgroundNodes[i], frame: badgeFrame)
transition.updateFrameAdditive(node: self.badgeTextNodes[i], frame: CGRect(origin: CGPoint(x: badgeFrame.minX + floor((badgeFrame.width - badgeTextSize.width) / 2.0) + 0.5, y: badgeFrame.minY + floor((badgeFrame.height - badgeTextSize.height) / 2.0) + 0.5), size: badgeTextSize))
contentY += textSize.height
}
if firstTime {
self.animationNode.play()
}
return super.hitTest(point, with: event)
}
}