mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
Temporary QR codes
This commit is contained in:
@@ -33,6 +33,7 @@ import TelegramUniversalVideoContent
|
||||
import GalleryUI
|
||||
import SaveToCameraRoll
|
||||
import SegmentedControlNode
|
||||
import AnimatedCountLabelNode
|
||||
|
||||
private func closeButtonImage(theme: PresentationTheme) -> UIImage? {
|
||||
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 let disposable = MetaDisposable()
|
||||
private let contactDisposable = MetaDisposable()
|
||||
private var currentContactToken: ExportedContactToken?
|
||||
|
||||
var present: ((ViewController) -> Void)?
|
||||
var previewTheme: ((String?, Bool?, PresentationTheme) -> Void)?
|
||||
@@ -1154,6 +1157,43 @@ private class ChatQrCodeScreenNode: ViewControllerTracingNode, UIScrollViewDeleg
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -1451,7 +1491,11 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
||||
private var codeForegroundDimNode: ASDisplayNode
|
||||
private let codeMaskNode: ASDisplayNode
|
||||
private let codeTextNode: ImmediateTextNode
|
||||
private let codeCountdownNode: ImmediateAnimatedCountLabelNode
|
||||
private let codeImageNode: TransformImageNode
|
||||
private let codeMarkersNode: TransformImageNode
|
||||
private var codeSnapshotView: UIView?
|
||||
private var codePlaceholderNode: AnimatedStickerNode
|
||||
private let codeIconBackgroundNode: ASImageNode
|
||||
private let codeStaticIconNode: ASImageNode?
|
||||
private let codeAnimatedIconNode: AnimatedStickerNode?
|
||||
@@ -1471,7 +1515,10 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
||||
}
|
||||
|
||||
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) {
|
||||
self.context = context
|
||||
@@ -1479,9 +1526,7 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
||||
self.threadId = threadId
|
||||
self.isStatic = isStatic
|
||||
self.temporary = temporary
|
||||
|
||||
self.setupTimestamp = CACurrentMediaTime()
|
||||
|
||||
|
||||
self.containerNode = ASDisplayNode()
|
||||
|
||||
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.codeImageNode = TransformImageNode()
|
||||
self.codeMarkersNode = TransformImageNode()
|
||||
self.codeIconBackgroundNode = ASImageNode()
|
||||
|
||||
self.codePlaceholderNode = DefaultAnimatedStickerNodeImpl()
|
||||
|
||||
if isStatic {
|
||||
let codeStaticIconNode = ASImageNode()
|
||||
codeStaticIconNode.displaysAsynchronously = false
|
||||
@@ -1521,27 +1569,41 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
||||
}
|
||||
|
||||
var codeText: String
|
||||
if temporary {
|
||||
codeText = "5:00"
|
||||
if let addressName = peer.addressName, !addressName.isEmpty {
|
||||
codeText = "@\(peer.addressName ?? "")".uppercased()
|
||||
} else {
|
||||
if let addressName = peer.addressName, !addressName.isEmpty {
|
||||
codeText = "@\(peer.addressName ?? "")".uppercased()
|
||||
} else {
|
||||
codeText = peer.debugDisplayTitle.uppercased()
|
||||
}
|
||||
if let threadId = self.threadId, threadId != 0 {
|
||||
codeText += "/\(threadId)"
|
||||
}
|
||||
codeText = peer.debugDisplayTitle.uppercased()
|
||||
}
|
||||
if let threadId = self.threadId, threadId != 0 {
|
||||
codeText += "/\(threadId)"
|
||||
}
|
||||
|
||||
let codeFont = Font.with(size: 23.0, design: .round, weight: .bold, traits: [.monospacedNumbers])
|
||||
|
||||
self.codeTextNode = ImmediateTextNode()
|
||||
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.maximumNumberOfLines = 2
|
||||
self.codeTextNode.textAlignment = .center
|
||||
|
||||
self.codeCountdownNode = ImmediateAnimatedCountLabelNode()
|
||||
self.codeCountdownNode.alwaysOneDirection = true
|
||||
|
||||
if isStatic {
|
||||
if temporary {
|
||||
self.codeCountdownNode.isHidden = true
|
||||
}
|
||||
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()
|
||||
@@ -1560,8 +1622,13 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
||||
self.codeForegroundNode.addSubnode(self.codeForegroundDimNode)
|
||||
|
||||
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.codeTextNode)
|
||||
self.codeMaskNode.addSubnode(self.codeCountdownNode)
|
||||
|
||||
self.containerNode.addSubnode(self.avatarNode)
|
||||
|
||||
@@ -1607,6 +1674,13 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
||||
self?.tick()
|
||||
}, queue: Queue.mainQueue())
|
||||
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() {
|
||||
let timeout: Double = 5.0 * 60.0
|
||||
let currentTime = CACurrentMediaTime()
|
||||
let elapsed = max(0.0, timeout - (currentTime - self.setupTimestamp))
|
||||
let currentTime = Int32(Date().timeIntervalSince1970)
|
||||
let elapsed: Int32
|
||||
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 {
|
||||
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) {
|
||||
@@ -1640,6 +1785,9 @@ private class QrContentNode: ASDisplayNode, ContentNode {
|
||||
let scale: CGFloat = 3.0
|
||||
|
||||
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) {
|
||||
view.contentScaleFactor = scale
|
||||
@@ -1763,10 +1911,23 @@ 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)
|
||||
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))
|
||||
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))
|
||||
|
||||
if let qrCodeSize = self.qrCodeSize {
|
||||
|
||||
Reference in New Issue
Block a user