mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Temporary QR codes
This commit is contained in:
parent
dc4a04daa7
commit
3caedf5670
BIN
Telegram/Telegram-iOS/Resources/QrDataRain.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/QrDataRain.tgs
Normal file
Binary file not shown.
Binary file not shown.
@ -79,6 +79,7 @@ public class AnimatedCountLabelNode: ASDisplayNode {
|
|||||||
fileprivate var resolvedSegments: [ResolvedSegment.Key: (ResolvedSegment, TextNode)] = [:]
|
fileprivate var resolvedSegments: [ResolvedSegment.Key: (ResolvedSegment, TextNode)] = [:]
|
||||||
|
|
||||||
public var reverseAnimationDirection: Bool = false
|
public var reverseAnimationDirection: Bool = false
|
||||||
|
public var alwaysOneDirection: Bool = false
|
||||||
|
|
||||||
override public init() {
|
override public init() {
|
||||||
super.init()
|
super.init()
|
||||||
@ -91,6 +92,7 @@ public class AnimatedCountLabelNode: ASDisplayNode {
|
|||||||
segmentLayouts[segmentKey] = TextNode.asyncLayout(segmentAndTextNode.1)
|
segmentLayouts[segmentKey] = TextNode.asyncLayout(segmentAndTextNode.1)
|
||||||
}
|
}
|
||||||
let reverseAnimationDirection = self.reverseAnimationDirection
|
let reverseAnimationDirection = self.reverseAnimationDirection
|
||||||
|
let alwaysOneDirection = self.alwaysOneDirection
|
||||||
|
|
||||||
return { [weak self] size, initialSegments in
|
return { [weak self] size, initialSegments in
|
||||||
var segments: [ResolvedSegment] = []
|
var segments: [ResolvedSegment] = []
|
||||||
@ -173,7 +175,7 @@ public class AnimatedCountLabelNode: ASDisplayNode {
|
|||||||
fromAlpha = CGFloat(presentation.opacity)
|
fromAlpha = CGFloat(presentation.opacity)
|
||||||
}
|
}
|
||||||
var offsetY: CGFloat
|
var offsetY: CGFloat
|
||||||
if currentValue > updatedValue {
|
if currentValue > updatedValue || alwaysOneDirection {
|
||||||
offsetY = -floor(currentTextNode.bounds.height * 0.6)
|
offsetY = -floor(currentTextNode.bounds.height * 0.6)
|
||||||
} else {
|
} else {
|
||||||
offsetY = floor(currentTextNode.bounds.height * 0.6)
|
offsetY = floor(currentTextNode.bounds.height * 0.6)
|
||||||
|
@ -38,7 +38,7 @@ public func qrCodeCutout(size: Int, dimensions: CGSize, scale: CGFloat?) -> (Int
|
|||||||
return (cutoutSize, cutoutRect, quadSize)
|
return (cutoutSize, cutoutRect, quadSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = nil, icon: QrCodeIcon, ecl: String = "M") -> Signal<(Int, (TransformImageArguments) -> DrawingContext?), NoError> {
|
public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = nil, icon: QrCodeIcon, ecl: String = "M", onlyMarkers: Bool = false) -> Signal<(Int, (TransformImageArguments) -> DrawingContext?), NoError> {
|
||||||
return Signal<(Data, Int, Int), NoError> { subscriber in
|
return Signal<(Data, Int, Int), NoError> { subscriber in
|
||||||
if let data = string.data(using: .isoLatin1, allowLossyConversion: false), let filter = CIFilter(name: "CIQRCodeGenerator") {
|
if let data = string.data(using: .isoLatin1, allowLossyConversion: false), let filter = CIFilter(name: "CIQRCodeGenerator") {
|
||||||
filter.setValue(data, forKey: "inputMessage")
|
filter.setValue(data, forKey: "inputMessage")
|
||||||
@ -182,6 +182,7 @@ public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !onlyMarkers {
|
||||||
for y in 0 ..< size {
|
for y in 0 ..< size {
|
||||||
for x in 0 ..< size {
|
for x in 0 ..< size {
|
||||||
if (y < markerSize + 1 && (x < markerSize + 1 || x > size - markerSize - 2)) || (y > size - markerSize - 2 && x < markerSize + 1) {
|
if (y < markerSize + 1 && (x < markerSize + 1 || x > size - markerSize - 2)) || (y > size - markerSize - 2 && x < markerSize + 1) {
|
||||||
@ -225,6 +226,7 @@ public func qrCode(string: String, color: UIColor, backgroundColor: UIColor? = n
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
c.translateBy(x: padding, y: padding)
|
c.translateBy(x: padding, y: padding)
|
||||||
|
|
||||||
|
@ -2,3 +2,34 @@ import Foundation
|
|||||||
import Postbox
|
import Postbox
|
||||||
import TelegramApi
|
import TelegramApi
|
||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
|
|
||||||
|
func _internal_importContactToken(account: Account, token: String) -> Signal<EnginePeer?, NoError> {
|
||||||
|
return account.network.request(Api.functions.contacts.importContactToken(token: token))
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Api.User?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> map { result -> EnginePeer? in
|
||||||
|
return result.flatMap { EnginePeer(TelegramUser(user: $0)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct ExportedContactToken {
|
||||||
|
public let url: String
|
||||||
|
public let expires: Int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func _internal_exportContactToken(account: Account) -> Signal<ExportedContactToken?, NoError> {
|
||||||
|
return account.network.request(Api.functions.contacts.exportContactToken())
|
||||||
|
|> map(Optional.init)
|
||||||
|
|> `catch` { _ -> Signal<Api.ExportedContactToken?, NoError> in
|
||||||
|
return .single(nil)
|
||||||
|
}
|
||||||
|
|> map { result -> ExportedContactToken? in
|
||||||
|
if let result = result, case let .exportedContactToken(url, expires) = result {
|
||||||
|
return ExportedContactToken(url: url, expires: expires)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -973,6 +973,14 @@ public extension TelegramEngine {
|
|||||||
public func forumChannelTopicNotificationExceptions(id: EnginePeer.Id) -> Signal<[EngineMessageHistoryThread.NotificationException], NoError> {
|
public func forumChannelTopicNotificationExceptions(id: EnginePeer.Id) -> Signal<[EngineMessageHistoryThread.NotificationException], NoError> {
|
||||||
return _internal_forumChannelTopicNotificationExceptions(account: self.account, id: id)
|
return _internal_forumChannelTopicNotificationExceptions(account: self.account, id: id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func importContactToken(token: String) -> Signal<EnginePeer?, NoError> {
|
||||||
|
return _internal_importContactToken(account: self.account, token: token)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func exportContactToken() -> Signal<ExportedContactToken?, NoError> {
|
||||||
|
return _internal_exportContactToken(account: self.account)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import TelegramUniversalVideoContent
|
|||||||
import GalleryUI
|
import GalleryUI
|
||||||
import SaveToCameraRoll
|
import SaveToCameraRoll
|
||||||
import SegmentedControlNode
|
import SegmentedControlNode
|
||||||
|
import AnimatedCountLabelNode
|
||||||
|
|
||||||
private func closeButtonImage(theme: PresentationTheme) -> UIImage? {
|
private func closeButtonImage(theme: PresentationTheme) -> UIImage? {
|
||||||
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
||||||
@ -787,6 +788,8 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
private var containerLayout: (ContainerViewLayout, CGFloat)?
|
||||||
|
|
||||||
private let disposable = MetaDisposable()
|
private let disposable = MetaDisposable()
|
||||||
|
private let contactDisposable = MetaDisposable()
|
||||||
|
private var currentContactToken: ExportedContactToken?
|
||||||
|
|
||||||
var present: ((ViewController) -> Void)?
|
var present: ((ViewController) -> Void)?
|
||||||
var previewTheme: ((String?, Bool?, PresentationTheme) -> Void)?
|
var previewTheme: ((String?, Bool?, PresentationTheme) -> Void)?
|
||||||
@ -1154,6 +1157,43 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.ready.set(self.contentNode.isReady)
|
self.ready.set(self.contentNode.isReady)
|
||||||
|
|
||||||
|
if case let .peer(_, _, temporary) = controller.subject, temporary {
|
||||||
|
self.contactDisposable.set(
|
||||||
|
(context.engine.peers.exportContactToken()
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] token in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.currentContactToken = token
|
||||||
|
if let contentNode = strongSelf.contentNode as? QrContentNode, let token = token {
|
||||||
|
contentNode.setContactToken(token, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
if let contentNode = self.contentNode as? QrContentNode {
|
||||||
|
contentNode.requestNextToken = { [weak self] in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.contactDisposable.set(
|
||||||
|
(context.engine.peers.exportContactToken()
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] token in
|
||||||
|
if let strongSelf = self {
|
||||||
|
strongSelf.currentContactToken = token
|
||||||
|
if let contentNode = strongSelf.contentNode as? QrContentNode, let token = token {
|
||||||
|
contentNode.setContactToken(token, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.disposable.dispose()
|
||||||
|
self.contactDisposable.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func enqueueTransition(_ transition: ThemeSettingsThemeItemNodeTransition) {
|
private func enqueueTransition(_ transition: ThemeSettingsThemeItemNodeTransition) {
|
||||||
@ -1451,7 +1491,11 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
|||||||
private var codeForegroundDimNode: ASDisplayNode
|
private var codeForegroundDimNode: ASDisplayNode
|
||||||
private let codeMaskNode: ASDisplayNode
|
private let codeMaskNode: ASDisplayNode
|
||||||
private let codeTextNode: ImmediateTextNode
|
private let codeTextNode: ImmediateTextNode
|
||||||
|
private let codeCountdownNode: ImmediateAnimatedCountLabelNode
|
||||||
private let codeImageNode: TransformImageNode
|
private let codeImageNode: TransformImageNode
|
||||||
|
private let codeMarkersNode: TransformImageNode
|
||||||
|
private var codeSnapshotView: UIView?
|
||||||
|
private var codePlaceholderNode: AnimatedStickerNode
|
||||||
private let codeIconBackgroundNode: ASImageNode
|
private let codeIconBackgroundNode: ASImageNode
|
||||||
private let codeStaticIconNode: ASImageNode?
|
private let codeStaticIconNode: ASImageNode?
|
||||||
private let codeAnimatedIconNode: AnimatedStickerNode?
|
private let codeAnimatedIconNode: AnimatedStickerNode?
|
||||||
@ -1471,7 +1515,10 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var timer: SwiftSignalKit.Timer?
|
private var timer: SwiftSignalKit.Timer?
|
||||||
private var setupTimestamp: Double
|
private var token: ExportedContactToken?
|
||||||
|
private var gettingNextToken = false
|
||||||
|
private var tokenUpdated = false
|
||||||
|
var requestNextToken: () -> Void = {}
|
||||||
|
|
||||||
init(context: AccountContext, peer: Peer, threadId: Int64?, isStatic: Bool = false, temporary: Bool) {
|
init(context: AccountContext, peer: Peer, threadId: Int64?, isStatic: Bool = false, temporary: Bool) {
|
||||||
self.context = context
|
self.context = context
|
||||||
@ -1480,8 +1527,6 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
|||||||
self.isStatic = isStatic
|
self.isStatic = isStatic
|
||||||
self.temporary = temporary
|
self.temporary = temporary
|
||||||
|
|
||||||
self.setupTimestamp = CACurrentMediaTime()
|
|
||||||
|
|
||||||
self.containerNode = ASDisplayNode()
|
self.containerNode = ASDisplayNode()
|
||||||
|
|
||||||
self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false, useExperimentalImplementation: context.sharedContext.immediateExperimentalUISettings.experimentalBackground)
|
self.wallpaperBackgroundNode = createWallpaperBackgroundNode(context: context, forChatDisplay: true, useSharedAnimationPhase: false, useExperimentalImplementation: context.sharedContext.immediateExperimentalUISettings.experimentalBackground)
|
||||||
@ -1503,8 +1548,11 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
|||||||
self.codeMaskNode = ASDisplayNode()
|
self.codeMaskNode = ASDisplayNode()
|
||||||
|
|
||||||
self.codeImageNode = TransformImageNode()
|
self.codeImageNode = TransformImageNode()
|
||||||
|
self.codeMarkersNode = TransformImageNode()
|
||||||
self.codeIconBackgroundNode = ASImageNode()
|
self.codeIconBackgroundNode = ASImageNode()
|
||||||
|
|
||||||
|
self.codePlaceholderNode = DefaultAnimatedStickerNodeImpl()
|
||||||
|
|
||||||
if isStatic {
|
if isStatic {
|
||||||
let codeStaticIconNode = ASImageNode()
|
let codeStaticIconNode = ASImageNode()
|
||||||
codeStaticIconNode.displaysAsynchronously = false
|
codeStaticIconNode.displaysAsynchronously = false
|
||||||
@ -1521,9 +1569,6 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var codeText: String
|
var codeText: String
|
||||||
if temporary {
|
|
||||||
codeText = "5:00"
|
|
||||||
} else {
|
|
||||||
if let addressName = peer.addressName, !addressName.isEmpty {
|
if let addressName = peer.addressName, !addressName.isEmpty {
|
||||||
codeText = "@\(peer.addressName ?? "")".uppercased()
|
codeText = "@\(peer.addressName ?? "")".uppercased()
|
||||||
} else {
|
} else {
|
||||||
@ -1532,16 +1577,33 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
|||||||
if let threadId = self.threadId, threadId != 0 {
|
if let threadId = self.threadId, threadId != 0 {
|
||||||
codeText += "/\(threadId)"
|
codeText += "/\(threadId)"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
let codeFont = Font.with(size: 23.0, design: .round, weight: .bold, traits: [.monospacedNumbers])
|
||||||
|
|
||||||
self.codeTextNode = ImmediateTextNode()
|
self.codeTextNode = ImmediateTextNode()
|
||||||
self.codeTextNode.displaysAsynchronously = false
|
self.codeTextNode.displaysAsynchronously = false
|
||||||
self.codeTextNode.attributedText = NSAttributedString(string: codeText, font: Font.with(size: 23.0, design: .round, weight: .bold, traits: [.monospacedNumbers]), textColor: .black)
|
self.codeTextNode.attributedText = NSAttributedString(string: codeText, font: codeFont, textColor: .black)
|
||||||
self.codeTextNode.truncationMode = .byCharWrapping
|
self.codeTextNode.truncationMode = .byCharWrapping
|
||||||
self.codeTextNode.maximumNumberOfLines = 2
|
self.codeTextNode.maximumNumberOfLines = 2
|
||||||
self.codeTextNode.textAlignment = .center
|
self.codeTextNode.textAlignment = .center
|
||||||
|
|
||||||
|
self.codeCountdownNode = ImmediateAnimatedCountLabelNode()
|
||||||
|
self.codeCountdownNode.alwaysOneDirection = true
|
||||||
|
|
||||||
if isStatic {
|
if isStatic {
|
||||||
|
if temporary {
|
||||||
|
self.codeCountdownNode.isHidden = true
|
||||||
|
}
|
||||||
self.codeTextNode.setNeedsDisplayAtScale(3.0)
|
self.codeTextNode.setNeedsDisplayAtScale(3.0)
|
||||||
|
} else if temporary {
|
||||||
|
self.codeTextNode.isHidden = true
|
||||||
|
|
||||||
|
self.codeCountdownNode.segments = [
|
||||||
|
.number(Int(5), NSAttributedString(string: "5", font: codeFont, textColor: .black)),
|
||||||
|
.text(0, NSAttributedString(string: ":", font: codeFont, textColor: .black)),
|
||||||
|
.number(Int(0), NSAttributedString(string: "0", font: codeFont, textColor: .black)),
|
||||||
|
.number(Int(0), NSAttributedString(string: "0", font: codeFont, textColor: .black))
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
self.avatarNode = ImageNode()
|
self.avatarNode = ImageNode()
|
||||||
@ -1560,8 +1622,13 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
|||||||
self.codeForegroundNode.addSubnode(self.codeForegroundDimNode)
|
self.codeForegroundNode.addSubnode(self.codeForegroundDimNode)
|
||||||
|
|
||||||
self.codeMaskNode.addSubnode(self.codeImageNode)
|
self.codeMaskNode.addSubnode(self.codeImageNode)
|
||||||
|
self.codeMaskNode.addSubnode(self.codeMarkersNode)
|
||||||
|
if temporary {
|
||||||
|
self.codeMaskNode.addSubnode(self.codePlaceholderNode)
|
||||||
|
}
|
||||||
self.codeMaskNode.addSubnode(self.codeIconBackgroundNode)
|
self.codeMaskNode.addSubnode(self.codeIconBackgroundNode)
|
||||||
self.codeMaskNode.addSubnode(self.codeTextNode)
|
self.codeMaskNode.addSubnode(self.codeTextNode)
|
||||||
|
self.codeMaskNode.addSubnode(self.codeCountdownNode)
|
||||||
|
|
||||||
self.containerNode.addSubnode(self.avatarNode)
|
self.containerNode.addSubnode(self.avatarNode)
|
||||||
|
|
||||||
@ -1607,6 +1674,13 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
|||||||
self?.tick()
|
self?.tick()
|
||||||
}, queue: Queue.mainQueue())
|
}, queue: Queue.mainQueue())
|
||||||
self.timer?.start()
|
self.timer?.start()
|
||||||
|
|
||||||
|
self.codePlaceholderNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "QrDataRain"), width: 512, height: 512, playbackMode: .loop, mode: .direct(cachePathPrefix: nil))
|
||||||
|
self.codePlaceholderNode.visibility = true
|
||||||
|
|
||||||
|
self.codeImageNode.alpha = 0.0
|
||||||
|
|
||||||
|
self.codeMarkersNode.setSignal(qrCode(string: "https://t.me/contact/000000:abcdef", color: .black, backgroundColor: nil, icon: .cutout, ecl: "Q", onlyMarkers: true) |> map { $0.1 }, attemptSynchronously: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1621,14 +1695,85 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func tick() {
|
private func tick() {
|
||||||
let timeout: Double = 5.0 * 60.0
|
let currentTime = Int32(Date().timeIntervalSince1970)
|
||||||
let currentTime = CACurrentMediaTime()
|
let elapsed: Int32
|
||||||
let elapsed = max(0.0, timeout - (currentTime - self.setupTimestamp))
|
if let token = self.token {
|
||||||
|
elapsed = token.expires - currentTime
|
||||||
|
} else {
|
||||||
|
elapsed = 300
|
||||||
|
}
|
||||||
|
|
||||||
|
let string = stringForDuration(max(0, elapsed))
|
||||||
|
|
||||||
|
let codeFont = Font.with(size: 23.0, design: .round, weight: .bold, traits: [.monospacedNumbers])
|
||||||
|
|
||||||
|
var segments: [AnimatedCountLabelNode.Segment] = []
|
||||||
|
for char in string {
|
||||||
|
if let intValue = Int(String(char)) {
|
||||||
|
segments.append(.number(intValue, NSAttributedString(string: String(char), font: codeFont, textColor: .black)))
|
||||||
|
} else {
|
||||||
|
segments.append(.text(0, NSAttributedString(string: String(char), font: codeFont, textColor: .black)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.codeCountdownNode.segments = segments
|
||||||
|
|
||||||
self.codeTextNode.attributedText = NSAttributedString(string: stringForDuration(Int32(elapsed)), font: Font.with(size: 23.0, design: .round, weight: .bold, traits: [.monospacedNumbers]), textColor: .black)
|
|
||||||
if let (size, topInset, bottomInset) = self.validLayout {
|
if let (size, topInset, bottomInset) = self.validLayout {
|
||||||
self.updateLayout(size: size, topInset: topInset, bottomInset: bottomInset, transition: .immediate)
|
self.updateLayout(size: size, topInset: topInset, bottomInset: bottomInset, transition: .immediate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if elapsed <= 1 {
|
||||||
|
if !self.gettingNextToken {
|
||||||
|
self.gettingNextToken = true
|
||||||
|
self.requestNextToken()
|
||||||
|
|
||||||
|
let codeSnapshotView = UIImageView(image: self.codeImageNode.image)
|
||||||
|
codeSnapshotView.frame = self.codeImageNode.frame
|
||||||
|
self.codeImageNode.view.superview?.addSubview(codeSnapshotView)
|
||||||
|
self.codeSnapshotView = codeSnapshotView
|
||||||
|
|
||||||
|
self.codeImageNode.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.tokenUpdated {
|
||||||
|
self.tokenUpdated = false
|
||||||
|
|
||||||
|
self.codeImageNode.isHidden = false
|
||||||
|
self.codeImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||||
|
if let codeSnapshotView = self.codeSnapshotView {
|
||||||
|
self.codeSnapshotView = nil
|
||||||
|
codeSnapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { [weak codeSnapshotView] _ in
|
||||||
|
codeSnapshotView?.removeFromSuperview()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setContactToken(_ token: ExportedContactToken, animated: Bool) {
|
||||||
|
self.token = token
|
||||||
|
if self.gettingNextToken {
|
||||||
|
self.gettingNextToken = false
|
||||||
|
self.tokenUpdated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
self.codeImageNode.setSignal(qrCode(string: token.url, color: .black, backgroundColor: nil, icon: .cutout, ecl: "Q") |> beforeNext { [weak self] size, _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.qrCodeSize = size
|
||||||
|
if let (size, topInset, bottomInset) = strongSelf.validLayout {
|
||||||
|
strongSelf.updateLayout(size: size, topInset: topInset, bottomInset: bottomInset, transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strongSelf.codePlaceholderNode.visibility {
|
||||||
|
strongSelf.codeImageNode.alpha = 1.0
|
||||||
|
strongSelf.codeImageNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
strongSelf.codePlaceholderNode.alpha = 0.0
|
||||||
|
strongSelf.codePlaceholderNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in
|
||||||
|
self?.codePlaceholderNode.visibility = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
} |> map { $0.1 }, attemptSynchronously: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateImage(completion: @escaping (UIImage?) -> Void) {
|
func generateImage(completion: @escaping (UIImage?) -> Void) {
|
||||||
@ -1640,6 +1785,9 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
|||||||
let scale: CGFloat = 3.0
|
let scale: CGFloat = 3.0
|
||||||
|
|
||||||
let copyNode = QrContentNode(context: self.context, peer: self.peer, threadId: self.threadId, isStatic: true, temporary: false)
|
let copyNode = QrContentNode(context: self.context, peer: self.peer, threadId: self.threadId, isStatic: true, temporary: false)
|
||||||
|
if let token = self.token {
|
||||||
|
copyNode.setContactToken(token, animated: false)
|
||||||
|
}
|
||||||
|
|
||||||
func prepare(view: UIView, scale: CGFloat) {
|
func prepare(view: UIView, scale: CGFloat) {
|
||||||
view.contentScaleFactor = scale
|
view.contentScaleFactor = scale
|
||||||
@ -1764,9 +1912,22 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
|||||||
let imageFrame = CGRect(origin: CGPoint(x: floor((codeBackgroundFrame.width - imageSize.width) / 2.0), y: floor((codeBackgroundFrame.width - imageSize.height) / 2.0)), size: imageSize)
|
let imageFrame = CGRect(origin: CGPoint(x: floor((codeBackgroundFrame.width - imageSize.width) / 2.0), y: floor((codeBackgroundFrame.width - imageSize.height) / 2.0)), size: imageSize)
|
||||||
transition.updateFrame(node: self.codeImageNode, frame: imageFrame)
|
transition.updateFrame(node: self.codeImageNode, frame: imageFrame)
|
||||||
|
|
||||||
|
let makeMarkersLayout = self.codeMarkersNode.asyncLayout()
|
||||||
|
let markersApply = makeMarkersLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil, scale: self.isStatic ? 3.0 : nil ))
|
||||||
|
let _ = markersApply()
|
||||||
|
|
||||||
|
transition.updateFrame(node: self.codeMarkersNode, frame: imageFrame)
|
||||||
|
|
||||||
|
let codePlaceholderFrame = imageFrame.insetBy(dx: 10.0, dy: 10.0)
|
||||||
|
self.codePlaceholderNode.updateLayout(size: codePlaceholderFrame.size)
|
||||||
|
self.codePlaceholderNode.frame = codePlaceholderFrame
|
||||||
|
|
||||||
let codeTextSize = self.codeTextNode.updateLayout(CGSize(width: codeBackgroundFrame.width - floor(imageFrame.minX * 1.2), height: codeBackgroundFrame.height))
|
let codeTextSize = self.codeTextNode.updateLayout(CGSize(width: codeBackgroundFrame.width - floor(imageFrame.minX * 1.2), height: codeBackgroundFrame.height))
|
||||||
transition.updateFrame(node: self.codeTextNode, frame: CGRect(origin: CGPoint(x: floor((codeBackgroundFrame.width - codeTextSize.width) / 2.0), y: imageFrame.maxY + floor((codeBackgroundHeight - imageFrame.maxY - codeTextSize.height) / 2.0) - 5.0), size: codeTextSize))
|
transition.updateFrame(node: self.codeTextNode, frame: CGRect(origin: CGPoint(x: floor((codeBackgroundFrame.width - codeTextSize.width) / 2.0), y: imageFrame.maxY + floor((codeBackgroundHeight - imageFrame.maxY - codeTextSize.height) / 2.0) - 5.0), size: codeTextSize))
|
||||||
|
|
||||||
|
let codeCountdownSize = self.codeCountdownNode.updateLayout(size: CGSize(width: codeBackgroundFrame.width - floor(imageFrame.minX * 1.2), height: codeBackgroundFrame.height), animated: true)
|
||||||
|
transition.updateFrame(node: self.codeCountdownNode, frame: CGRect(origin: CGPoint(x: floor((codeBackgroundFrame.width - codeCountdownSize.width) / 2.0), y: imageFrame.maxY + floor((codeBackgroundHeight - imageFrame.maxY - codeCountdownSize.height) / 2.0) - 5.0), size: codeCountdownSize))
|
||||||
|
|
||||||
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0), y: codeBackgroundFrame.minY - floor(avatarSize.height * 0.7)), size: avatarSize))
|
transition.updateFrame(node: self.avatarNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - avatarSize.width) / 2.0), y: codeBackgroundFrame.minY - floor(avatarSize.height * 0.7)), size: avatarSize))
|
||||||
|
|
||||||
if let qrCodeSize = self.qrCodeSize {
|
if let qrCodeSize = self.qrCodeSize {
|
||||||
|
@ -556,6 +556,22 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur
|
|||||||
convertedUrl = "https://t.me/login/\(code)"
|
convertedUrl = "https://t.me/login/\(code)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if parsedUrl.host == "contact" {
|
||||||
|
if let components = URLComponents(string: "/?" + query) {
|
||||||
|
var token: String?
|
||||||
|
if let queryItems = components.queryItems {
|
||||||
|
for queryItem in queryItems {
|
||||||
|
if let value = queryItem.value {
|
||||||
|
if queryItem.name == "token" {
|
||||||
|
token = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let token = token {
|
||||||
|
convertedUrl = "https://t.me/contact/\(token)"
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if parsedUrl.host == "confirmphone" {
|
} else if parsedUrl.host == "confirmphone" {
|
||||||
if let components = URLComponents(string: "/?" + query) {
|
if let components = URLComponents(string: "/?" + query) {
|
||||||
var phone: String?
|
var phone: String?
|
||||||
|
@ -7024,11 +7024,11 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
threadId = Int64(message.messageId.id)
|
threadId = Int64(message.messageId.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// var temporary = false
|
var temporary = false
|
||||||
// if self.isSettings && self.data?.globalSettings?.privacySettings?.phoneDiscoveryEnabled == false {
|
if self.isSettings && self.data?.globalSettings?.privacySettings?.phoneDiscoveryEnabled == false {
|
||||||
// temporary = true
|
temporary = true
|
||||||
// }
|
}
|
||||||
controller.present(ChatQrCodeScreen(context: self.context, subject: .peer(peer: peer, threadId: threadId, temporary: false)), in: .window(.root))
|
controller.present(ChatQrCodeScreen(context: self.context, subject: .peer(peer: peer, threadId: threadId, temporary: temporary)), in: .window(.root))
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate func openSettings(section: PeerInfoSettingsSection) {
|
fileprivate func openSettings(section: PeerInfoSettingsSection) {
|
||||||
|
@ -94,6 +94,7 @@ public enum ParsedInternalUrl {
|
|||||||
case theme(String)
|
case theme(String)
|
||||||
case phone(String, String?, String?)
|
case phone(String, String?, String?)
|
||||||
case startAttach(String, String?, String?)
|
case startAttach(String, String?, String?)
|
||||||
|
case contactToken(String)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ParsedUrl {
|
private enum ParsedUrl {
|
||||||
@ -160,7 +161,7 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
if let _ = url {
|
if let _ = url {
|
||||||
return .internalInstantView(url: "https://t.me/\(query)")
|
return .internalInstantView(url: "https://t.me/\(query)")
|
||||||
}
|
}
|
||||||
} else if peerName == "login" {
|
} else if peerName == "contact" {
|
||||||
var code: String?
|
var code: String?
|
||||||
for queryItem in queryItems {
|
for queryItem in queryItems {
|
||||||
if let value = queryItem.value {
|
if let value = queryItem.value {
|
||||||
@ -290,6 +291,8 @@ public func parseInternalUrl(query: String) -> ParsedInternalUrl? {
|
|||||||
if let code = Int(pathComponents[1]) {
|
if let code = Int(pathComponents[1]) {
|
||||||
return .confirmationCode(code)
|
return .confirmationCode(code)
|
||||||
}
|
}
|
||||||
|
} else if peerName == "contact" {
|
||||||
|
return .contactToken(pathComponents[1])
|
||||||
} else if pathComponents[0] == "share" && pathComponents[1] == "url" {
|
} else if pathComponents[0] == "share" && pathComponents[1] == "url" {
|
||||||
if let queryItems = components.queryItems {
|
if let queryItems = components.queryItems {
|
||||||
var url: String?
|
var url: String?
|
||||||
@ -655,6 +658,15 @@ private func resolveInternalUrl(context: AccountContext, url: ParsedInternalUrl)
|
|||||||
return .single(.inaccessiblePeer)
|
return .single(.inaccessiblePeer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case let .contactToken(token):
|
||||||
|
return context.engine.peers.importContactToken(token: token)
|
||||||
|
|> mapToSignal { peer -> Signal<ResolvedUrl?, NoError> in
|
||||||
|
if let peer = peer {
|
||||||
|
return .single(.peer(peer._asPeer(), .info))
|
||||||
|
} else {
|
||||||
|
return .single(.peer(nil, .info))
|
||||||
|
}
|
||||||
|
}
|
||||||
case let .privateMessage(messageId, threadId, timecode):
|
case let .privateMessage(messageId, threadId, timecode):
|
||||||
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId))
|
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: messageId.peerId))
|
||||||
|> mapToSignal { peer -> Signal<ResolvedUrl?, NoError> in
|
|> mapToSignal { peer -> Signal<ResolvedUrl?, NoError> in
|
||||||
|
Loading…
x
Reference in New Issue
Block a user