Improve QR auth UI

This commit is contained in:
Ali 2019-12-07 00:51:43 +04:00
parent 12c99de957
commit 3671fc3761
16 changed files with 3191 additions and 3016 deletions

View File

@ -5143,11 +5143,15 @@ Any member of this group will be able to see messages in the channel.";
"Settings.Devices" = "Devices";
"Settings.AddDevice" = "Scan QR";
"AuthSessions.DevicesTitle" = "Devices";
"AuthSessions.AddDevice" = "Scan QR";
"AuthSessions.AddDevice" = "Add Device";
"AuthSessions.AddDevice.ScanInfo" = "Scan a QR code to log into\nthis account on another device.";
"AuthSessions.AddDevice.ScanTitle" = "Scan QR Code";
"AuthSessions.AddDevice.ScanApps" = "Telegram is available for\niPhone, iPad, macOS, Windows and Linux";
"AuthSessions.AddDevice.ConfirmDevice" = "Confirm Log In";
"AuthSessions.AddDevice.InvalidQRCode" = "Invalid QR Code";
"AuthSessions.AddDeviceIntro.Title" = "Log in by QR Code";
"AuthSessions.AddDeviceIntro.Text1" = "[Download Telegram]() on your computer";
"AuthSessions.AddDeviceIntro.Text2" = "Run Telegram on your computer to get the QR code";
"AuthSessions.AddDeviceIntro.Text3" = "Scan the QR code to connect your account";
"AuthSessions.AddDeviceIntro.Action" = "Scan QR Code";
"Map.SendThisPlace" = "Send This Place";
"Map.SetThisPlace" = "Set This Place";
@ -5161,3 +5165,5 @@ Any member of this group will be able to see messages in the channel.";
"ChatList.Search.ShowMore" = "Show more";
"ChatList.Search.ShowLess" = "Show less";
"AuthSessions.OtherDevices" = "The official Telegram App is available for iPhone, iPad, Android, macOS, Windows and Linux. [Learn More]()";

View File

@ -21,6 +21,7 @@ static_library(
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/OverlayStatusController:OverlayStatusController",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
"//submodules/Markdown:Markdown",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

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)
}
}

View File

@ -79,6 +79,10 @@ public final class AuthTransferScanScreen: ViewController {
private var inForegroundDisposable: Disposable?
private let approveDisposable = MetaDisposable()
private var controllerNode: AuthTransferScanScreenNode {
return self.displayNode as! AuthTransferScanScreenNode
}
public init(context: AccountContext, activeSessionsContext: ActiveSessionsContext?) {
self.context = context
self.activeSessionsContext = activeSessionsContext
@ -134,7 +138,10 @@ public final class AuthTransferScanScreen: ViewController {
return .single(code)
|> delay(0.5, queue: Queue.mainQueue())
}).start(next: { [weak self] code in
guard let strongSelf = self, let code = code else {
guard let strongSelf = self else {
return
}
guard let code = code else {
return
}
if let url = URL(string: code), let parsedToken = parseAuthTransferUrl(url) {
@ -143,32 +150,30 @@ public final class AuthTransferScanScreen: ViewController {
guard let strongSelf = self else {
return
}
(strongSelf.displayNode as! AuthTransferScanScreenNode).updateTokenPreview(confirmationNode: AuthTransferConfirmationNode(context: strongSelf.context, presentationData: strongSelf.presentationData, tokenInfo: tokenInfo, accept: {
strongSelf.approveDisposable.set((approveAuthTransferToken(account: strongSelf.context.account, token: parsedToken)
|> deliverOnMainQueue).start(error: { _ in
guard let strongSelf = self else {
return
}
strongSelf.approveDisposable.set((approveAuthTransferToken(account: strongSelf.context.account, token: parsedToken)
|> deliverOnMainQueue).start(error: { _ in
guard let strongSelf = self else {
return
}
(strongSelf.displayNode as! AuthTransferScanScreenNode).updateTokenPreview(confirmationNode: nil)
}, completed: {
guard let strongSelf = self else {
return
}
let activeSessionsContext = strongSelf.activeSessionsContext
Queue.mainQueue().after(1.5, {
activeSessionsContext?.loadMore()
})
strongSelf.dismiss()
}))
}, cancel: {
strongSelf.controllerNode.codeWithError = code
strongSelf.controllerNode.updateFocusedRect(nil)
}, completed: {
guard let strongSelf = self else {
return
}
(strongSelf.displayNode as! AuthTransferScanScreenNode).updateTokenPreview(confirmationNode: nil)
strongSelf.controllerNode.codeWithError = nil
let activeSessionsContext = strongSelf.activeSessionsContext
Queue.mainQueue().after(1.5, {
activeSessionsContext?.loadMore()
})
strongSelf.dismiss()
}))
}, error: { _ in
guard let strongSelf = self else {
return
}
strongSelf.controllerNode.codeWithError = code
strongSelf.controllerNode.updateFocusedRect(nil)
})
}
})
@ -195,7 +200,7 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
private let torchButtonNode: GlassButtonNode
private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let descriptionNode: ImmediateTextNode
private let errorTextNode: ImmediateTextNode
private let camera: Camera
private let codeDisposable = MetaDisposable()
@ -203,10 +208,20 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
fileprivate let focusedCode = ValuePromise<CameraCode?>(ignoreRepeated: true)
private var focusedRect: CGRect?
private(set) var confirmationNode: AuthTransferConfirmationNode?
private var validLayout: (ContainerViewLayout, CGFloat)?
var codeWithError: String? {
didSet {
if self.codeWithError != oldValue {
if self.codeWithError != nil {
self.errorTextNode.isHidden = false
} else {
self.errorTextNode.isHidden = true
}
}
}
}
init(presentationData: PresentationData) {
self.presentationData = presentationData
@ -254,11 +269,12 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
self.textNode.maximumNumberOfLines = 0
self.textNode.textAlignment = .center
self.descriptionNode = ImmediateTextNode()
self.descriptionNode.displaysAsynchronously = false
self.descriptionNode.attributedText = NSAttributedString(string: presentationData.strings.AuthSessions_AddDevice_ScanApps, font: Font.regular(14.0), textColor: .white)
self.descriptionNode.maximumNumberOfLines = 0
self.descriptionNode.textAlignment = .center
self.errorTextNode = ImmediateTextNode()
self.errorTextNode.displaysAsynchronously = false
self.errorTextNode.attributedText = NSAttributedString(string: presentationData.strings.AuthSessions_AddDevice_InvalidQRCode, font: Font.medium(16.0), textColor: .white)
self.errorTextNode.maximumNumberOfLines = 0
self.errorTextNode.textAlignment = .center
//self.errorTextNode.isHidden = true
self.camera = Camera(configuration: .init(preset: .hd1920x1080, position: .back, audio: false))
@ -274,10 +290,10 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
self.addSubnode(self.rightDimNode)
self.addSubnode(self.centerDimNode)
self.addSubnode(self.frameNode)
//self.addSubnode(self.torchButtonNode)
self.addSubnode(self.torchButtonNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.addSubnode(self.descriptionNode)
self.addSubnode(self.errorTextNode)
self.torchButtonNode.addTarget(self, action: #selector(self.torchPressed), forControlEvents: .touchUpInside)
}
@ -313,44 +329,31 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
}
let filteredCodes = codes.filter { $0.message.hasPrefix("tg://") }
if let code = filteredCodes.first, CGRect(x: 0.3, y: 0.3, width: 0.4, height: 0.4).contains(code.boundingBox.center) {
strongSelf.focusedCode.set(code)
strongSelf.updateFocusedRect(code.boundingBox)
if strongSelf.codeWithError != code.message {
strongSelf.codeWithError = nil
}
if strongSelf.codeWithError == code.message {
strongSelf.focusedCode.set(nil)
strongSelf.updateFocusedRect(nil)
} else {
strongSelf.focusedCode.set(code)
strongSelf.updateFocusedRect(code.boundingBox)
}
} else {
strongSelf.codeWithError = nil
strongSelf.focusedCode.set(nil)
strongSelf.updateFocusedRect(nil)
}
}))
}
private func updateFocusedRect(_ rect: CGRect?) {
func updateFocusedRect(_ rect: CGRect?) {
self.focusedRect = rect
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring))
}
}
func updateTokenPreview(confirmationNode: AuthTransferConfirmationNode?) {
if let confirmationNode = self.confirmationNode {
confirmationNode.animateOut { [weak confirmationNode] in
confirmationNode?.removeFromSupernode()
}
self.confirmationNode = nil
}
self.confirmationNode = confirmationNode
if let confirmationNode = self.confirmationNode {
self.addSubnode(confirmationNode)
if let (layout, navigationHeight) = self.validLayout {
confirmationNode.updateLayout(layout: layout, transition: .immediate)
confirmationNode.animateIn()
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
} else {
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationHeight)
@ -379,19 +382,7 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
let controlsAlpha: CGFloat
var centerDimAlpha: CGFloat = 0.0
var frameAlpha: CGFloat = 1.0
if self.confirmationNode != nil {
controlsAlpha = 0.0
dimAlpha = 0.625
centerDimAlpha = 0.625
frameAlpha = 0.0
if let focusedRect = self.focusedRect {
let side = max(bounds.width * focusedRect.width, bounds.height * focusedRect.height) * 0.6
let center = CGPoint(x: (1.0 - focusedRect.center.y) * bounds.width, y: focusedRect.center.x * bounds.height)
dimRect = CGRect(x: center.x - side / 2.0, y: center.y - side / 2.0, width: side, height: side)
} else {
dimRect = CGRect(x: dimInset, y: dimHeight, width: layout.size.width - dimInset * 2.0, height: layout.size.height - dimHeight * 2.0)
}
} else if let focusedRect = self.focusedRect {
if let focusedRect = self.focusedRect {
controlsAlpha = 0.0
dimAlpha = 1.0
let side = max(bounds.width * focusedRect.width, bounds.height * focusedRect.height) * 0.6
@ -418,26 +409,22 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr
transition.updateFrame(node: self.centerDimNode, frame: dimRect)
let buttonSize = CGSize(width: 72.0, height: 72.0)
transition.updateFrame(node: self.torchButtonNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0), y: dimHeight + frameSide + 50.0), size: buttonSize))
transition.updateFrame(node: self.torchButtonNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0), y: dimHeight + frameSide + 98.0), size: buttonSize))
transition.updateAlpha(node: self.titleNode, alpha: controlsAlpha)
transition.updateAlpha(node: self.textNode, alpha: controlsAlpha)
transition.updateAlpha(node: self.descriptionNode, alpha: controlsAlpha)
transition.updateAlpha(node: self.errorTextNode, alpha: controlsAlpha)
transition.updateAlpha(node: self.torchButtonNode, alpha: controlsAlpha)
let titleSize = self.titleNode.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))
let descriptionSize = self.descriptionNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let errorTextSize = self.errorTextNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: dimHeight - textSize.height - titleSpacing), size: textSize)
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: textFrame.minY - 18.0 - titleSize.height), size: titleSize)
let descriptionFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - descriptionSize.width) / 2.0), y: layout.size.height - dimHeight + titleSpacing), size: descriptionSize)
let errorTextFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - errorTextSize.width) / 2.0), y: dimHeight + frameSide + 48.0), size: errorTextSize)
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
transition.updateFrameAdditive(node: self.descriptionNode, frame: descriptionFrame)
if let confirmationNode = self.confirmationNode {
confirmationNode.updateLayout(layout: layout, transition: transition)
}
transition.updateFrameAdditive(node: self.errorTextNode, frame: errorTextFrame)
}
@objc private func torchPressed() {

View File

@ -293,7 +293,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry {
arguments.openTwoStepVerification(data)
})
case let .activeSessions(theme, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Sessions")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/MenuIcons/Websites")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: {
arguments.openActiveSessions()
})
case let .accountHeader(theme, text):

View File

@ -24,7 +24,9 @@ private final class RecentSessionsControllerArguments {
let addDevice: () -> Void
init(account: Account, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, terminateOtherSessions: @escaping () -> Void, removeWebSession: @escaping (Int64) -> Void, terminateAllWebSessions: @escaping () -> Void, addDevice: @escaping () -> Void) {
let openOtherAppsUrl: () -> Void
init(account: Account, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, terminateOtherSessions: @escaping () -> Void, removeWebSession: @escaping (Int64) -> Void, terminateAllWebSessions: @escaping () -> Void, addDevice: @escaping () -> Void, openOtherAppsUrl: @escaping () -> Void) {
self.account = account
self.setSessionIdWithRevealedOptions = setSessionIdWithRevealedOptions
self.removeSession = removeSession
@ -34,6 +36,8 @@ private final class RecentSessionsControllerArguments {
self.terminateAllWebSessions = terminateAllWebSessions
self.addDevice = addDevice
self.openOtherAppsUrl = openOtherAppsUrl
}
}
@ -51,32 +55,7 @@ private enum RecentSessionsSection: Int32 {
private enum RecentSessionsEntryStableId: Hashable {
case session(Int64)
case index(Int32)
var hashValue: Int {
switch self {
case let .session(hash):
return hash.hashValue
case let .index(index):
return index.hashValue
}
}
static func ==(lhs: RecentSessionsEntryStableId, rhs: RecentSessionsEntryStableId) -> Bool {
switch lhs {
case let .session(hash):
if case .session(hash) = rhs {
return true
} else {
return false
}
case let .index(index):
if case .index(index) = rhs {
return true
} else {
return false
}
}
}
case devicesInfo
}
private enum RecentSessionsEntry: ItemListNodeEntry {
@ -92,215 +71,243 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
case addDevice(PresentationTheme, String)
case session(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editing: Bool, revealed: Bool)
case website(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool)
case devicesInfo(PresentationTheme, String)
var section: ItemListSectionId {
switch self {
case .currentSessionHeader, .currentSession, .terminateOtherSessions, .terminateAllWebSessions, .currentSessionInfo:
return RecentSessionsSection.currentSession.rawValue
case .pendingSessionsHeader, .pendingSession, .pendingSessionsInfo:
return RecentSessionsSection.pendingSessions.rawValue
case .otherSessionsHeader, .addDevice, .session, .website:
return RecentSessionsSection.otherSessions.rawValue
case .currentSessionHeader, .currentSession, .terminateOtherSessions, .terminateAllWebSessions, .currentSessionInfo:
return RecentSessionsSection.currentSession.rawValue
case .pendingSessionsHeader, .pendingSession, .pendingSessionsInfo:
return RecentSessionsSection.pendingSessions.rawValue
case .otherSessionsHeader, .addDevice, .session, .website, .devicesInfo:
return RecentSessionsSection.otherSessions.rawValue
}
}
var stableId: RecentSessionsEntryStableId {
switch self {
case .currentSessionHeader:
return .index(0)
case .currentSession:
return .index(1)
case .terminateOtherSessions:
return .index(2)
case .terminateAllWebSessions:
return .index(3)
case .currentSessionInfo:
return .index(4)
case .pendingSessionsHeader:
return .index(5)
case let .pendingSession(_, _, _, _, session, _, _, _):
return .session(session.hash)
case .pendingSessionsInfo:
return .index(6)
case .otherSessionsHeader:
return .index(7)
case .addDevice:
return .index(8)
case let .session(_, _, _, _, session, _, _, _):
return .session(session.hash)
case let .website(_, _, _, _, _, website, _, _, _, _):
return .session(website.hash)
case .currentSessionHeader:
return .index(0)
case .currentSession:
return .index(1)
case .terminateOtherSessions:
return .index(2)
case .terminateAllWebSessions:
return .index(3)
case .currentSessionInfo:
return .index(4)
case .pendingSessionsHeader:
return .index(5)
case let .pendingSession(_, _, _, _, session, _, _, _):
return .session(session.hash)
case .pendingSessionsInfo:
return .index(6)
case .otherSessionsHeader:
return .index(7)
case .addDevice:
return .index(8)
case let .session(_, _, _, _, session, _, _, _):
return .session(session.hash)
case let .website(_, _, _, _, _, website, _, _, _, _):
return .session(website.hash)
case .devicesInfo:
return .devicesInfo
}
}
static func ==(lhs: RecentSessionsEntry, rhs: RecentSessionsEntry) -> Bool {
switch lhs {
case let .currentSessionHeader(lhsTheme, lhsText):
if case let .currentSessionHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .terminateOtherSessions(lhsTheme, lhsText):
if case let .terminateOtherSessions(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .terminateAllWebSessions(lhsTheme, lhsText):
if case let .terminateAllWebSessions(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .currentSessionInfo(lhsTheme, lhsText):
if case let .currentSessionInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .pendingSessionsHeader(lhsTheme, lhsText):
if case let .pendingSessionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .pendingSession(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsSession, lhsEnabled, lhsEditing, lhsRevealed):
if case let .pendingSession(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsSession, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsSession == rhsSession, lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed {
return true
} else {
return false
}
case let .pendingSessionsInfo(lhsTheme, lhsText):
if case let .pendingSessionsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .otherSessionsHeader(lhsTheme, lhsText):
if case let .otherSessionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .addDevice(lhsTheme, lhsText):
if case let .addDevice(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .currentSession(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsSession):
if case let .currentSession(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsSession) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsSession == rhsSession {
return true
} else {
return false
}
case let .session(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsSession, lhsEnabled, lhsEditing, lhsRevealed):
if case let .session(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsSession, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsSession == rhsSession, lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed {
return true
} else {
return false
}
case let .website(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsWebsite, lhsPeer, lhsEnabled, lhsEditing, lhsRevealed):
if case let .website(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsWebsite, rhsPeer, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameOrder == rhsNameOrder, lhsWebsite == rhsWebsite, arePeersEqual(lhsPeer, rhsPeer), lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed {
return true
} else {
return false
}
case let .currentSessionHeader(lhsTheme, lhsText):
if case let .currentSessionHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .terminateOtherSessions(lhsTheme, lhsText):
if case let .terminateOtherSessions(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .terminateAllWebSessions(lhsTheme, lhsText):
if case let .terminateAllWebSessions(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .currentSessionInfo(lhsTheme, lhsText):
if case let .currentSessionInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .pendingSessionsHeader(lhsTheme, lhsText):
if case let .pendingSessionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .pendingSession(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsSession, lhsEnabled, lhsEditing, lhsRevealed):
if case let .pendingSession(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsSession, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsSession == rhsSession, lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed {
return true
} else {
return false
}
case let .pendingSessionsInfo(lhsTheme, lhsText):
if case let .pendingSessionsInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .otherSessionsHeader(lhsTheme, lhsText):
if case let .otherSessionsHeader(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .addDevice(lhsTheme, lhsText):
if case let .addDevice(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .currentSession(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsSession):
if case let .currentSession(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsSession) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsSession == rhsSession {
return true
} else {
return false
}
case let .session(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsSession, lhsEnabled, lhsEditing, lhsRevealed):
if case let .session(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsSession, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsSession == rhsSession, lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed {
return true
} else {
return false
}
case let .website(lhsIndex, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsNameOrder, lhsWebsite, lhsPeer, lhsEnabled, lhsEditing, lhsRevealed):
if case let .website(rhsIndex, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsNameOrder, rhsWebsite, rhsPeer, rhsEnabled, rhsEditing, rhsRevealed) = rhs, lhsIndex == rhsIndex, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsNameOrder == rhsNameOrder, lhsWebsite == rhsWebsite, arePeersEqual(lhsPeer, rhsPeer), lhsEnabled == rhsEnabled, lhsEditing == rhsEditing, lhsRevealed == rhsRevealed {
return true
} else {
return false
}
case let .devicesInfo(lhsTheme, lhsText):
if case let .devicesInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
}
}
static func <(lhs: RecentSessionsEntry, rhs: RecentSessionsEntry) -> Bool {
switch lhs.stableId {
case let .index(lhsIndex):
if case let .index(rhsIndex) = rhs.stableId {
return lhsIndex <= rhsIndex
case let .index(lhsIndex):
if case let .index(rhsIndex) = rhs.stableId {
return lhsIndex <= rhsIndex
} else {
if case .pendingSession = rhs, lhsIndex > 5 {
return false
} else {
if case .pendingSession = rhs, lhsIndex > 5 {
return false
return true
}
}
case .session:
switch lhs {
case let .session(lhsIndex, _, _, _, _, _, _, _):
if case let .session(rhsIndex, _, _, _, _, _, _, _) = rhs {
return lhsIndex <= rhsIndex
} else if case .devicesInfo = rhs.stableId {
return true
} else {
return false
}
case let .pendingSession(lhsIndex, _, _, _, _, _, _, _):
if case let .pendingSession(rhsIndex, _, _, _, _, _, _, _) = rhs {
return lhsIndex <= rhsIndex
} else if case .session = rhs {
return true
} else if case .devicesInfo = rhs.stableId {
return true
} else {
if case let .index(rhsIndex) = rhs.stableId {
return rhsIndex == 6
} else {
return true
return false
}
}
case .session:
switch lhs {
case let .session(lhsIndex, _, _, _, _, _, _, _):
if case let .session(rhsIndex, _, _, _, _, _, _, _) = rhs {
return lhsIndex <= rhsIndex
} else {
return false
}
case let .pendingSession(lhsIndex, _, _, _, _, _, _, _):
if case let .pendingSession(rhsIndex, _, _, _, _, _, _, _) = rhs {
return lhsIndex <= rhsIndex
} else if case .session = rhs {
return true
} else {
if case let .index(rhsIndex) = rhs.stableId {
return rhsIndex == 6
} else {
return false
}
}
case let .website(lhsIndex, _, _, _, _, _, _, _, _, _):
if case let .website(rhsIndex, _, _, _, _, _, _, _, _, _) = rhs {
return lhsIndex <= rhsIndex
} else {
return false
}
default:
preconditionFailure()
case let .website(lhsIndex, _, _, _, _, _, _, _, _, _):
if case let .website(rhsIndex, _, _, _, _, _, _, _, _, _) = rhs {
return lhsIndex <= rhsIndex
} else if case .devicesInfo = rhs.stableId {
return true
} else {
return false
}
default:
preconditionFailure()
}
case .devicesInfo:
if case .devicesInfo = rhs.stableId {
return false
} else {
return false
}
}
}
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
let arguments = arguments as! RecentSessionsControllerArguments
switch self {
case let .currentSessionHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .currentSession(theme, strings, dateTimeFormat, session):
return ItemListRecentSessionItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, session: session, enabled: true, editable: false, editing: false, revealed: false, sectionId: self.section, setSessionIdWithRevealedOptions: { _, _ in
}, removeSession: { _ in
})
case let .terminateOtherSessions(theme, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.terminateOtherSessions()
})
case let .terminateAllWebSessions(theme, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.terminateAllWebSessions()
})
case let .currentSessionInfo(theme, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .pendingSessionsHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .pendingSession(_, theme, strings, dateTimeFormat, session, enabled, editing, revealed):
return ItemListRecentSessionItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
arguments.setSessionIdWithRevealedOptions(previousId, id)
}, removeSession: { id in
arguments.removeSession(id)
})
case let .pendingSessionsInfo(theme, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .otherSessionsHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .addDevice(theme, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.addDevice()
})
case let .session(_, theme, strings, dateTimeFormat, session, enabled, editing, revealed):
return ItemListRecentSessionItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
arguments.setSessionIdWithRevealedOptions(previousId, id)
}, removeSession: { id in
arguments.removeSession(id)
})
case let .website(_, theme, strings, dateTimeFormat, nameDisplayOrder, website, peer, enabled, editing, revealed):
return ItemListWebsiteItem(account: arguments.account, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, website: website, peer: peer, enabled: enabled, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
arguments.setSessionIdWithRevealedOptions(previousId, id)
}, removeSession: { id in
arguments.removeWebSession(id)
})
case let .currentSessionHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .currentSession(theme, strings, dateTimeFormat, session):
return ItemListRecentSessionItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, session: session, enabled: true, editable: false, editing: false, revealed: false, sectionId: self.section, setSessionIdWithRevealedOptions: { _, _ in
}, removeSession: { _ in
})
case let .terminateOtherSessions(theme, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.terminateOtherSessions()
})
case let .terminateAllWebSessions(theme, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.terminateAllWebSessions()
})
case let .currentSessionInfo(theme, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .pendingSessionsHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .pendingSession(_, theme, strings, dateTimeFormat, session, enabled, editing, revealed):
return ItemListRecentSessionItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
arguments.setSessionIdWithRevealedOptions(previousId, id)
}, removeSession: { id in
arguments.removeSession(id)
})
case let .pendingSessionsInfo(theme, text):
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .otherSessionsHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .addDevice(theme, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.addDevice()
})
case let .session(_, theme, strings, dateTimeFormat, session, enabled, editing, revealed):
return ItemListRecentSessionItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
arguments.setSessionIdWithRevealedOptions(previousId, id)
}, removeSession: { id in
arguments.removeSession(id)
})
case let .website(_, theme, strings, dateTimeFormat, nameDisplayOrder, website, peer, enabled, editing, revealed):
return ItemListWebsiteItem(account: arguments.account, theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, website: website, peer: peer, enabled: enabled, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
arguments.setSessionIdWithRevealedOptions(previousId, id)
}, removeSession: { id in
arguments.removeWebSession(id)
})
case let .devicesInfo(theme, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { action in
switch action {
case .tap:
arguments.openOtherAppsUrl()
}
})
}
}
}
@ -402,6 +409,10 @@ private func recentSessionsControllerEntries(presentationData: PresentationData,
entries.append(.session(index: Int32(i), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, session: filteredSessions[i], enabled: state.removingSessionId != filteredSessions[i].hash && !state.terminatingOtherSessions, editing: state.editing, revealed: state.sessionIdWithRevealedOptions == filteredSessions[i].hash))
}
}
if enableQRLogin {
entries.append(.devicesInfo(presentationData.theme, presentationData.strings.AuthSessions_OtherDevices))
}
}
}
@ -601,7 +612,9 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
])
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}, addDevice: {
pushControllerImpl?(AuthTransferScanScreen(context: context, activeSessionsContext: activeSessionsContext))
pushControllerImpl?(AuthDataTransferSplashScreen(context: context, activeSessionsContext: activeSessionsContext))
}, openOtherAppsUrl: {
context.sharedContext.openExternalUrl(context: context, urlContext: .generic, url: "https://telegram.org/desktop", forceExternal: true, presentationData: context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
})
let websitesSignal: Signal<([WebAuthorization], [PeerId : Peer])?, NoError> = .single(nil) |> then(webSessions(network: context.account.network) |> map(Optional.init))

View File

@ -213,6 +213,7 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
case savedMessages(PresentationTheme, UIImage?, String)
case recentCalls(PresentationTheme, UIImage?, String)
case stickers(PresentationTheme, UIImage?, String, String, [ArchivedStickerPackItem]?)
case contentStickers(PresentationTheme, UIImage?, String, String, [ArchivedStickerPackItem]?)
case notificationsAndSounds(PresentationTheme, UIImage?, String, NotificationExceptionsList?, Bool)
case privacyAndSecurity(PresentationTheme, UIImage?, String, AccountPrivacySettings?)
@ -240,7 +241,7 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
return SettingsSection.media.rawValue
case .savedMessages, .recentCalls, .stickers:
return SettingsSection.media.rawValue
case .notificationsAndSounds, .privacyAndSecurity, .dataAndStorage, .themes, .language:
case .notificationsAndSounds, .privacyAndSecurity, .dataAndStorage, .themes, .language, .contentStickers:
return SettingsSection.generalSettings.rawValue
case .passport, .wallet, .watch :
return SettingsSection.advanced.rawValue
@ -287,16 +288,18 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
return 1011
case .language:
return 1012
case .wallet:
case .contentStickers:
return 1013
case .passport:
case .wallet:
return 1014
case .watch:
case .passport:
return 1015
case .askAQuestion:
case .watch:
return 1016
case .faq:
case .askAQuestion:
return 1017
case .faq:
return 1018
}
}
@ -412,6 +415,12 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
} else {
return false
}
case let .contentStickers(lhsTheme, lhsImage, lhsText, lhsValue, _):
if case let .contentStickers(rhsTheme, rhsImage, rhsText, rhsValue, _) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .notificationsAndSounds(lhsTheme, lhsImage, lhsText, lhsExceptionsList, lhsWarning):
if case let .notificationsAndSounds(rhsTheme, rhsImage, rhsText, rhsExceptionsList, rhsWarning) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsExceptionsList == rhsExceptionsList, lhsWarning == rhsWarning {
return true
@ -559,6 +568,10 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: value, labelStyle: .badge(theme.list.itemAccentColor), sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openStickerPacks(archivedPacks)
}, clearHighlightAutomatically: false)
case let .contentStickers(theme, image, text, value, archivedPacks):
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: value, labelStyle: .badge(theme.list.itemAccentColor), sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openStickerPacks(archivedPacks)
}, clearHighlightAutomatically: false)
case let .notificationsAndSounds(theme, image, text, exceptionsList, warning):
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: warning ? "!" : "", labelStyle: warning ? .badge(theme.list.itemDestructiveColor) : .text, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openNotificationsAndSounds(exceptionsList)
@ -657,9 +670,10 @@ private func settingsEntries(account: Account, presentationData: PresentationDat
entries.append(.savedMessages(presentationData.theme, PresentationResourcesSettings.savedMessages, presentationData.strings.Settings_SavedMessages))
entries.append(.recentCalls(presentationData.theme, PresentationResourcesSettings.recentCalls, presentationData.strings.CallSettings_RecentCalls))
entries.append(.stickers(presentationData.theme, PresentationResourcesSettings.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks))
if enableQRLogin {
entries.append(.devices(presentationData.theme, UIImage(bundleImageName: "Settings/MenuIcons/Sessions")?.precomposed(), presentationData.strings.Settings_Devices, otherSessionCount == 0 ? presentationData.strings.Settings_AddDevice : "\(otherSessionCount)"))
} else {
entries.append(.stickers(presentationData.theme, PresentationResourcesSettings.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks))
}
let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed)
@ -669,6 +683,9 @@ private func settingsEntries(account: Account, presentationData: PresentationDat
entries.append(.themes(presentationData.theme, PresentationResourcesSettings.appearance, presentationData.strings.Settings_Appearance))
let languageName = presentationData.strings.primaryComponent.localizedName
entries.append(.language(presentationData.theme, PresentationResourcesSettings.language, presentationData.strings.Settings_AppLanguage, languageName.isEmpty ? presentationData.strings.Localization_LanguageName : languageName))
if enableQRLogin {
entries.append(.contentStickers(presentationData.theme, PresentationResourcesSettings.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks))
}
if hasWallet {
entries.append(.wallet(presentationData.theme, PresentationResourcesSettings.wallet, "Gram Wallet", ""))
@ -1093,7 +1110,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
|> deliverOnMainQueue
|> take(1)).start(next: { activeSessionsContext, count in
if count == 0 {
pushControllerImpl?(AuthTransferScanScreen(context: context, activeSessionsContext: activeSessionsContext))
pushControllerImpl?(AuthDataTransferSplashScreen(context: context, activeSessionsContext: activeSessionsContext))
} else {
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext))
}

View File

@ -319,9 +319,9 @@ final class ChatHistoryPreloadManager {
guard let strongSelf = self else {
return
}
#if DEBUG
/*#if DEBUG
return
#endif
#endif*/
var indices: [(ChatHistoryPreloadIndex, Bool, Bool)] = []
for entry in view.0.entries {

View File

@ -59,4 +59,6 @@ public struct PresentationResourcesSettings {
public static let setPasscode = renderIcon(name: "Settings/MenuIcons/SetPasscode")
public static let clearCache = renderIcon(name: "Settings/MenuIcons/ClearCache")
public static let changePhoneNumber = renderIcon(name: "Settings/MenuIcons/ChangePhoneNumber")
public static let websites = renderIcon(name: "Settings/MenuIcons/Websites")
}

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_activewebsites@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_activewebsites@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -152,6 +152,9 @@ final class HorizontalStickersChatContextPanelNode: ChatInputContextPanelNode {
override func didLoad() {
super.didLoad()
self.gridNode.view.disablesInteractiveTransitionGestureRecognizer = true
self.gridNode.view.disablesInteractiveKeyboardGestureRecognizer = true
self.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in
if let strongSelf = self {
let convertedPoint = strongSelf.gridNode.view.convert(point, from: strongSelf.view)

File diff suppressed because one or more lines are too long