diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index ff955dabf5..9b424bf106 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7131,3 +7131,9 @@ Sorry for the inconvenience."; "Conversation.LargeEmojiDisabledInfo" = "You have disabled large emoji, so they appear small and have no effects in chat."; "Conversation.LargeEmojiEnable" = "Enable Large Emoji"; "Conversation.LargeEmojiEnabled" = "Large emoji enabled."; + +"GroupInfo.QRCode.Info" = "Everyone on Telegram can scan this code to join this group."; +"ChannelInfo.QRCode.Info" = "Everyone on Telegram can scan this code to join this channel."; +"UserInfo.QRCode.InfoYou" = "Everyone on Telegram can scan this code to message you."; +"UserInfo.QRCode.InfoBot" = "Everyone on Telegram can scan this code to use this bot."; +"UserInfo.QRCode.InfoOther" = "Everyone on Telegram can scan this code to message %@."; diff --git a/submodules/InviteLinksUI/BUILD b/submodules/InviteLinksUI/BUILD index 528c7f5c20..05fbee64cb 100644 --- a/submodules/InviteLinksUI/BUILD +++ b/submodules/InviteLinksUI/BUILD @@ -56,6 +56,7 @@ swift_library( "//submodules/AvatarNode:AvatarNode", "//submodules/LocalizedPeerData:LocalizedPeerData", "//submodules/PeerInfoAvatarListNode:PeerInfoAvatarListNode", + "//submodules/QrCodeUI:QrCodeUI", ], visibility = [ "//visibility:public", diff --git a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift index fa3825f861..eb4bb08215 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift @@ -20,6 +20,7 @@ import OverlayStatusController import PresentationDataUtils import DirectionalPanGesture import UndoUI +import QrCodeUI class InviteLinkInviteInteraction { let context: AccountContext @@ -371,7 +372,7 @@ public final class InviteLinkInviteController: ViewController { isGroup = true } let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get()) - let controller = InviteLinkQRCodeController(context: context, updatedPresentationData: updatedPresentationData, invite: invite, isGroup: isGroup) + let controller = QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)) strongSelf.controller?.present(controller, in: .window(.root)) }) } diff --git a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift index eaf3fd7027..1b41a5d81f 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift @@ -20,6 +20,7 @@ import ItemListPeerActionItem import ItemListPeerItem import ShareController import UndoUI +import QrCodeUI private final class InviteLinkListControllerArguments { let context: AccountContext @@ -511,8 +512,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio } else { isGroup = true } - let controller = InviteLinkQRCodeController(context: context, updatedPresentationData: updatedPresentationData, invite: invite, isGroup: isGroup) - presentControllerImpl?(controller, nil) + presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), nil) }) }))) @@ -685,8 +685,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio isGroup = true } Queue.mainQueue().after(0.2) { - let controller = InviteLinkQRCodeController(context: context, updatedPresentationData: updatedPresentationData, invite: invite, isGroup: isGroup) - presentControllerImpl?(controller, nil) + presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), nil) } }) }))) diff --git a/submodules/InviteLinksUI/Sources/InviteLinkQRCodeController.swift b/submodules/InviteLinksUI/Sources/InviteLinkQRCodeController.swift deleted file mode 100644 index 056c07231a..0000000000 --- a/submodules/InviteLinksUI/Sources/InviteLinkQRCodeController.swift +++ /dev/null @@ -1,462 +0,0 @@ -import Foundation -import UIKit -import SwiftSignalKit -import TelegramPresentationData -import AppBundle -import AsyncDisplayKit -import Display -import QrCode -import AccountContext -import SolidRoundedButtonNode -import AnimatedStickerNode -import TelegramAnimatedStickerNode -import TelegramCore - -private func shareQrCode(context: AccountContext, link: String, view: UIView) { - let _ = (qrCode(string: link, color: .black, backgroundColor: .white, icon: .custom(UIImage(bundleImageName: "Chat/Links/QrLogo"))) - |> map { _, generator -> UIImage? in - let imageSize = CGSize(width: 768.0, height: 768.0) - let context = generator(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), scale: 1.0)) - return context?.generateImage() - } - |> deliverOnMainQueue).start(next: { image in - guard let image = image else { - return - } - - let activityController = UIActivityViewController(activityItems: [image], applicationActivities: nil) - if let window = view.window { - activityController.popoverPresentationController?.sourceView = window - activityController.popoverPresentationController?.sourceRect = CGRect(origin: CGPoint(x: window.bounds.width / 2.0, y: window.bounds.size.height - 1.0), size: CGSize(width: 1.0, height: 1.0)) - } - context.sharedContext.applicationBindings.presentNativeController(activityController) - }) -} - -public final class InviteLinkQRCodeController: ViewController { - private var controllerNode: Node { - return self.displayNode as! Node - } - - private var animatedIn = false - - private let context: AccountContext - private let invite: ExportedInvitation - private let isGroup: Bool - - private var presentationData: PresentationData - private var presentationDataDisposable: Disposable? - - private var initialBrightness: CGFloat? - private var brightnessArguments: (Double, Double, CGFloat, CGFloat)? - - private var animator: ConstantDisplayLinkAnimator? - - private let idleTimerExtensionDisposable = MetaDisposable() - - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, invite: ExportedInvitation, isGroup: Bool) { - self.context = context - self.invite = invite - self.isGroup = isGroup - - self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } - - super.init(navigationBarPresentationData: nil) - - self.statusBar.statusBarStyle = .Ignore - - self.blocksBackgroundWhenInOverlay = true - - self.presentationDataDisposable = ((updatedPresentationData?.signal ?? context.sharedContext.presentationData) - |> deliverOnMainQueue).start(next: { [weak self] presentationData in - if let strongSelf = self { - strongSelf.presentationData = presentationData - strongSelf.controllerNode.updatePresentationData(presentationData) - } - }) - - self.idleTimerExtensionDisposable.set(self.context.sharedContext.applicationBindings.pushIdleTimerExtension()) - - self.statusBar.statusBarStyle = .Ignore - - self.animator = ConstantDisplayLinkAnimator(update: { [weak self] in - self?.updateBrightness() - }) - self.animator?.isPaused = true - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.presentationDataDisposable?.dispose() - self.idleTimerExtensionDisposable.dispose() - self.animator?.invalidate() - } - - override public func loadDisplayNode() { - self.displayNode = Node(context: self.context, presentationData: self.presentationData, invite: self.invite, isGroup: self.isGroup) - self.controllerNode.dismiss = { [weak self] in - self?.presentingViewController?.dismiss(animated: false, completion: nil) - } - self.controllerNode.cancel = { [weak self] in - self?.dismiss() - } - } - - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - if !self.animatedIn { - self.animatedIn = true - self.controllerNode.animateIn() - - self.initialBrightness = UIScreen.main.brightness - self.brightnessArguments = (CACurrentMediaTime(), 0.3, UIScreen.main.brightness, 1.0) - self.updateBrightness() - } - } - - private func updateBrightness() { - if let (startTime, duration, initial, target) = self.brightnessArguments { - self.animator?.isPaused = false - - let t = CGFloat(max(0.0, min(1.0, (CACurrentMediaTime() - startTime) / duration))) - let value = initial + (target - initial) * t - - UIScreen.main.brightness = value - - if t >= 1.0 { - self.brightnessArguments = nil - self.animator?.isPaused = true - } - } else { - self.animator?.isPaused = true - } - } - - override public func dismiss(completion: (() -> Void)? = nil) { - if UIScreen.main.brightness > 0.99, let initialBrightness = self.initialBrightness { - self.brightnessArguments = (CACurrentMediaTime(), 0.3, UIScreen.main.brightness, initialBrightness) - self.updateBrightness() - } - - self.controllerNode.animateOut(completion: completion) - } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) - } - - class Node: ViewControllerTracingNode, UIScrollViewDelegate { - private let context: AccountContext - private let invite: ExportedInvitation - private var presentationData: PresentationData - - private let dimNode: ASDisplayNode - private let wrappingScrollNode: ASScrollNode - private let contentContainerNode: ASDisplayNode - private let backgroundNode: ASDisplayNode - private let contentBackgroundNode: ASDisplayNode - private let titleNode: ASTextNode - private let cancelButton: HighlightableButtonNode - - private let textNode: ImmediateTextNode - private let qrButtonNode: HighlightTrackingButtonNode - private let qrImageNode: TransformImageNode - private let qrIconNode: AnimatedStickerNode - private var qrCodeSize: Int? - private let buttonNode: SolidRoundedButtonNode - - private var containerLayout: (ContainerViewLayout, CGFloat)? - - var completion: ((Int32) -> Void)? - var dismiss: (() -> Void)? - var cancel: (() -> Void)? - - init(context: AccountContext, presentationData: PresentationData, invite: ExportedInvitation, isGroup: Bool) { - self.context = context - self.invite = invite - self.presentationData = presentationData - - self.wrappingScrollNode = ASScrollNode() - self.wrappingScrollNode.view.alwaysBounceVertical = true - self.wrappingScrollNode.view.delaysContentTouches = false - self.wrappingScrollNode.view.canCancelContentTouches = true - - self.dimNode = ASDisplayNode() - self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) - - self.contentContainerNode = ASDisplayNode() - self.contentContainerNode.isOpaque = false - - self.backgroundNode = ASDisplayNode() - self.backgroundNode.clipsToBounds = true - self.backgroundNode.cornerRadius = 16.0 - - let backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor - let textColor = self.presentationData.theme.actionSheet.primaryTextColor - let secondaryTextColor = self.presentationData.theme.actionSheet.secondaryTextColor - let accentColor = self.presentationData.theme.actionSheet.controlAccentColor - - self.contentBackgroundNode = ASDisplayNode() - self.contentBackgroundNode.backgroundColor = backgroundColor - - self.titleNode = ASTextNode() - self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.InviteLink_QRCode_Title, font: Font.bold(17.0), textColor: textColor) - - self.cancelButton = HighlightableButtonNode() - self.cancelButton.setTitle(self.presentationData.strings.Common_Done, with: Font.bold(17.0), with: accentColor, for: .normal) - - self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false) - - self.textNode = ImmediateTextNode() - self.textNode.maximumNumberOfLines = 3 - self.textNode.textAlignment = .center - - self.qrButtonNode = HighlightTrackingButtonNode() - self.qrImageNode = TransformImageNode() - self.qrImageNode.clipsToBounds = true - self.qrImageNode.cornerRadius = 16.0 - - self.qrIconNode = AnimatedStickerNode() - - self.qrIconNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "PlaneLogo"), width: 240, height: 240, mode: .direct(cachePathPrefix: nil)) - self.qrIconNode.visibility = true - - super.init() - - self.backgroundColor = nil - self.isOpaque = false - - self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:)))) - self.addSubnode(self.dimNode) - - self.wrappingScrollNode.view.delegate = self - self.addSubnode(self.wrappingScrollNode) - - self.wrappingScrollNode.addSubnode(self.backgroundNode) - self.wrappingScrollNode.addSubnode(self.contentContainerNode) - - self.backgroundNode.addSubnode(self.contentBackgroundNode) - self.contentContainerNode.addSubnode(self.titleNode) - self.contentContainerNode.addSubnode(self.cancelButton) - self.contentContainerNode.addSubnode(self.buttonNode) - - self.contentContainerNode.addSubnode(self.textNode) - self.contentContainerNode.addSubnode(self.qrImageNode) - self.contentContainerNode.addSubnode(self.qrIconNode) - self.contentContainerNode.addSubnode(self.qrButtonNode) - - self.textNode.attributedText = NSAttributedString(string: isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel, font: Font.regular(13.0), textColor: secondaryTextColor) - self.buttonNode.title = self.presentationData.strings.InviteLink_QRCode_Share - - self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside) - self.buttonNode.pressed = { [weak self] in - if let strongSelf = self{ - shareQrCode(context: strongSelf.context, link: strongSelf.invite.link, view: strongSelf.view) - } - } - - self.qrImageNode.setSignal(qrCode(string: self.invite.link, color: .black, backgroundColor: .white, icon: .cutout) |> beforeNext { [weak self] size, _ in - guard let strongSelf = self else { - return - } - strongSelf.qrCodeSize = size - if let (layout, navigationHeight) = strongSelf.containerLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationHeight, transition: .immediate) - } - } |> map { $0.1 }, attemptSynchronously: true) - - self.qrButtonNode.addTarget(self, action: #selector(self.qrPressed), forControlEvents: .touchUpInside) - self.qrButtonNode.highligthedChanged = { [weak self] highlighted in - guard let strongSelf = self else { - return - } - if highlighted { - strongSelf.qrImageNode.alpha = 0.4 - strongSelf.qrIconNode.alpha = 0.4 - } else { - strongSelf.qrImageNode.layer.animateAlpha(from: strongSelf.qrImageNode.alpha, to: 1.0, duration: 0.2) - strongSelf.qrImageNode.alpha = 1.0 - strongSelf.qrIconNode.layer.animateAlpha(from: strongSelf.qrIconNode.alpha, to: 1.0, duration: 0.2) - strongSelf.qrIconNode.alpha = 1.0 - } - } - } - - @objc private func qrPressed() { - shareQrCode(context: self.context, link: self.invite.link, view: self.view) - } - - func updatePresentationData(_ presentationData: PresentationData) { - let previousTheme = self.presentationData.theme - self.presentationData = presentationData - - self.contentBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor - self.titleNode.attributedText = NSAttributedString(string: self.titleNode.attributedText?.string ?? "", font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) - self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: Font.regular(13.0), textColor: self.presentationData.theme.actionSheet.secondaryTextColor) - - if previousTheme !== presentationData.theme, let (layout, navigationBarHeight) = self.containerLayout { - self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } - - self.cancelButton.setTitle(self.presentationData.strings.Common_Done, with: Font.bold(17.0), with: self.presentationData.theme.actionSheet.controlAccentColor, for: .normal) - self.buttonNode.updateTheme(SolidRoundedButtonTheme(theme: self.presentationData.theme)) - } - - override func didLoad() { - super.didLoad() - - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.wrappingScrollNode.view.contentInsetAdjustmentBehavior = .never - } - } - - @objc func cancelButtonPressed() { - self.cancel?() - } - - @objc func dimTapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - self.cancelButtonPressed() - } - } - - func animateIn() { - self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) - - let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - - let dimPosition = self.dimNode.layer.position - self.dimNode.layer.animatePosition(from: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), to: dimPosition, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - self.layer.animateBoundsOriginYAdditive(from: -offset, to: 0.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - } - - func animateOut(completion: (() -> Void)? = nil) { - var dimCompleted = false - var offsetCompleted = false - - let internalCompletion: () -> Void = { [weak self] in - if let strongSelf = self, dimCompleted && offsetCompleted { - strongSelf.dismiss?() - } - completion?() - } - - self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in - dimCompleted = true - internalCompletion() - }) - - let offset = self.bounds.size.height - self.contentBackgroundNode.frame.minY - let dimPosition = self.dimNode.layer.position - self.dimNode.layer.animatePosition(from: dimPosition, to: CGPoint(x: dimPosition.x, y: dimPosition.y - offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) - self.layer.animateBoundsOriginYAdditive(from: 0.0, to: -offset, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { _ in - offsetCompleted = true - internalCompletion() - }) - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if self.bounds.contains(point) { - if !self.contentBackgroundNode.bounds.contains(self.convert(point, to: self.contentBackgroundNode)) { - return self.dimNode.view - } - } - return super.hitTest(point, with: event) - } - - func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - let contentOffset = scrollView.contentOffset - let additionalTopHeight = max(0.0, -contentOffset.y) - - if additionalTopHeight >= 30.0 { - self.cancelButtonPressed() - } - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { - self.containerLayout = (layout, navigationBarHeight) - - var insets = layout.insets(options: [.statusBar, .input]) - insets.top = 32.0 - - let makeImageLayout = self.qrImageNode.asyncLayout() - let imageSide: CGFloat = 240.0 - let imageSize = CGSize(width: imageSide, height: imageSide) - let imageApply = makeImageLayout(TransformImageArguments(corners: ImageCorners(), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets(), emptyColor: nil)) - - let _ = imageApply() - - let width = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0) - - let imageFrame = CGRect(origin: CGPoint(x: floor((width - imageSize.width) / 2.0), y: insets.top + 16.0), size: imageSize) - transition.updateFrame(node: self.qrImageNode, frame: imageFrame) - transition.updateFrame(node: self.qrButtonNode, frame: imageFrame) - - if let qrCodeSize = self.qrCodeSize { - let (_, cutoutFrame, _) = qrCodeCutout(size: qrCodeSize, dimensions: imageSize, scale: nil) - self.qrIconNode.updateLayout(size: cutoutFrame.size) - transition.updateBounds(node: self.qrIconNode, bounds: CGRect(origin: CGPoint(), size: cutoutFrame.size)) - transition.updatePosition(node: self.qrIconNode, position: imageFrame.center.offsetBy(dx: 0.0, dy: -1.0)) - } - - let inset: CGFloat = 32.0 - var textSize = self.textNode.updateLayout(CGSize(width: width - inset * 2.0, height: CGFloat.greatestFiniteMagnitude)) - let textFrame = CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: imageFrame.maxY + 20.0), size: textSize) - transition.updateFrame(node: self.textNode, frame: textFrame) - - var textSpacing: CGFloat = 111.0 - if case .compact = layout.metrics.widthClass, layout.size.width > layout.size.height { - textSize = CGSize() - self.textNode.isHidden = true - textSpacing = 52.0 - } else { - self.textNode.isHidden = false - } - - let buttonSideInset: CGFloat = 16.0 - let bottomInset = insets.bottom + 10.0 - let buttonWidth = layout.size.width - buttonSideInset * 2.0 - let buttonHeight: CGFloat = 50.0 - - let buttonFrame = CGRect(origin: CGPoint(x: floor((width - buttonWidth) / 2.0), y: layout.size.height - bottomInset - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight)) - transition.updateFrame(node: self.buttonNode, frame: buttonFrame) - let _ = self.buttonNode.updateLayout(width: buttonFrame.width, transition: transition) - - let titleHeight: CGFloat = 54.0 - let contentHeight = titleHeight + textSize.height + imageSize.height + bottomInset + textSpacing - - let sideInset = floor((layout.size.width - width) / 2.0) - let contentContainerFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.size.height - contentHeight), size: CGSize(width: width, height: contentHeight)) - let contentFrame = contentContainerFrame - - var backgroundFrame = CGRect(origin: CGPoint(x: contentFrame.minX, y: contentFrame.minY), size: CGSize(width: contentFrame.width, height: contentFrame.height + 2000.0)) - if backgroundFrame.minY < contentFrame.minY { - backgroundFrame.origin.y = contentFrame.minY - } - transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) - transition.updateFrame(node: self.contentBackgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) - transition.updateFrame(node: self.wrappingScrollNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - - let titleSize = self.titleNode.measure(CGSize(width: width, height: titleHeight)) - let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 16.0), size: titleSize) - transition.updateFrame(node: self.titleNode, frame: titleFrame) - - let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleHeight)) - let cancelFrame = CGRect(origin: CGPoint(x: width - cancelSize.width - 16.0, y: 18.0), size: cancelSize) - transition.updateFrame(node: self.cancelButton, frame: cancelFrame) - - let buttonInset: CGFloat = 16.0 - let doneButtonHeight = self.buttonNode.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) - transition.updateFrame(node: self.buttonNode, frame: CGRect(x: buttonInset, y: contentHeight - doneButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: doneButtonHeight)) - - transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) - } - } -} diff --git a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift index e8bc9bd75b..eb85dc41e0 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift @@ -20,6 +20,7 @@ import OverlayStatusController import PresentationDataUtils import DirectionalPanGesture import UndoUI +import QrCodeUI class InviteLinkViewInteraction { let context: AccountContext @@ -597,8 +598,7 @@ public final class InviteLinkViewController: ViewController { isGroup = true } let updatedPresentationData = (strongSelf.presentationData, parentController.presentationDataPromise.get()) - let controller = InviteLinkQRCodeController(context: context, updatedPresentationData: updatedPresentationData, invite: invite, isGroup: isGroup) - strongSelf.controller?.present(controller, in: .window(.root)) + strongSelf.controller?.present(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), in: .window(.root)) }) }))) } diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index e5d81f45fb..9fa91d76db 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -21,6 +21,7 @@ import AccountContext import InviteLinksUI import ContextUI import UndoUI +import QrCodeUI private final class ChannelVisibilityControllerArguments { let context: AccountContext @@ -1086,8 +1087,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta } else { isGroup = true } - let controller = InviteLinkQRCodeController(context: context, updatedPresentationData: updatedPresentationData, invite: invite, isGroup: isGroup) - presentControllerImpl?(controller, nil) + presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), nil) }) } }) diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 9c938dc31b..5a73f815c6 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -25,6 +25,7 @@ public enum PresentationResourceKey: Int32 { case navigationMoreCircledIcon case navigationAddIcon case navigationPlayerCloseButton + case navigationQrCodeIcon case navigationLiveLocationIcon diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift index 8dadf06fa3..15a7e6b44c 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesRootController.swift @@ -98,6 +98,12 @@ public struct PresentationResourcesRootController { generateTintedImage(image: UIImage(bundleImageName: "Chat List/NavigationMore"), color: theme.rootController.navigationBar.accentTextColor) }) } + + public static func navigationQrCodeIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.navigationMoreCircledIcon.rawValue, { theme in + generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.rootController.navigationBar.accentTextColor) + }) + } public static func navigationAddIcon(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.navigationAddIcon.rawValue, { theme in diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index ec5812a2c6..dbecb9e167 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -249,6 +249,7 @@ swift_library( "//submodules/CodeInputView:CodeInputView", "//submodules/Components/ReactionButtonListComponent:ReactionButtonListComponent", "//submodules/InvisibleInkDustNode:InvisibleInkDustNode", + "//submodules/QrCodeUI:QrCodeUI", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift index 85b1d877e9..5e147457be 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/ListItems/PeerInfoScreenLabeledValueItem.swift @@ -3,6 +3,8 @@ import Display import TelegramPresentationData import AccountContext import TextFormat +import UIKit +import AppBundle enum PeerInfoScreenLabeledValueTextColor { case primary @@ -14,15 +16,21 @@ enum PeerInfoScreenLabeledValueTextBehavior: Equatable { case multiLine(maxLines: Int, enabledEntities: EnabledEntityTypes) } +enum PeerInfoScreenLabeledValueIcon { + case qrCode +} + final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem { let id: AnyHashable let label: String let text: String let textColor: PeerInfoScreenLabeledValueTextColor let textBehavior: PeerInfoScreenLabeledValueTextBehavior + let icon: PeerInfoScreenLabeledValueIcon? let action: (() -> Void)? let longTapAction: ((ASDisplayNode) -> Void)? let linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? + let iconAction: (() -> Void)? let requestLayout: () -> Void init( @@ -31,9 +39,11 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem { text: String, textColor: PeerInfoScreenLabeledValueTextColor = .primary, textBehavior: PeerInfoScreenLabeledValueTextBehavior = .singleLine, + icon: PeerInfoScreenLabeledValueIcon? = nil, action: (() -> Void)?, longTapAction: ((ASDisplayNode) -> Void)? = nil, linkItemAction: ((TextLinkItemActionType, TextLinkItem) -> Void)? = nil, + iconAction: (() -> Void)? = nil, requestLayout: @escaping () -> Void ) { self.id = id @@ -41,9 +51,11 @@ final class PeerInfoScreenLabeledValueItem: PeerInfoScreenItem { self.text = text self.textColor = textColor self.textBehavior = textBehavior + self.icon = icon self.action = action self.longTapAction = longTapAction self.linkItemAction = linkItemAction + self.iconAction = iconAction self.requestLayout = requestLayout } @@ -79,6 +91,9 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { private let expandNode: ImmediateTextNode private let expandButonNode: HighlightTrackingButtonNode + private let iconNode: ASImageNode + private let iconButtonNode: HighlightTrackingButtonNode + private var linkHighlightingNode: LinkHighlightingNode? private let activateArea: AccessibilityAreaNode @@ -116,6 +131,12 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.expandButonNode = HighlightTrackingButtonNode() + self.iconNode = ASImageNode() + self.iconNode.contentMode = .center + self.iconNode.displaysAsynchronously = false + + self.iconButtonNode = HighlightTrackingButtonNode() + self.activateArea = AccessibilityAreaNode() super.init() @@ -134,6 +155,9 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.addSubnode(self.expandNode) self.addSubnode(self.expandButonNode) + self.addSubnode(self.iconNode) + self.addSubnode(self.iconButtonNode) + self.addSubnode(self.activateArea) self.expandButonNode.addTarget(self, action: #selector(self.expandPressed), forControlEvents: .touchUpInside) @@ -148,6 +172,19 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { } } } + + self.iconButtonNode.addTarget(self, action: #selector(self.iconPressed), forControlEvents: .touchUpInside) + self.iconButtonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.iconNode.layer.removeAnimation(forKey: "opacity") + strongSelf.iconNode.alpha = 0.4 + } else { + strongSelf.iconNode.alpha = 1.0 + strongSelf.iconNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } } @objc private func expandPressed() { @@ -155,6 +192,10 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.item?.requestLayout() } + @objc private func iconPressed() { + self.item?.iconAction?() + } + override func didLoad() { super.didLoad() @@ -163,6 +204,9 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { guard let strongSelf = self, let item = strongSelf.item else { return .keepWithSingleTap } + if !strongSelf.iconButtonNode.isHidden, strongSelf.iconButtonNode.view.hitTest(strongSelf.view.convert(point, to: strongSelf.iconButtonNode.view), with: nil) != nil { + return .fail + } if !strongSelf.expandButonNode.isHidden, strongSelf.expandButonNode.view.hitTest(strongSelf.view.convert(point, to: strongSelf.expandButonNode.view), with: nil) != nil { return .fail } @@ -181,7 +225,10 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { guard let strongSelf = self else { return } - strongSelf.updateTouchesAtPoint(point) + if !strongSelf.iconButtonNode.isHidden, let point = point, strongSelf.iconButtonNode.frame.contains(point) { + } else { + strongSelf.updateTouchesAtPoint(point) + } } self.view.addGestureRecognizer(recognizer) } @@ -276,6 +323,20 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { self.expandButonNode.isHidden = true } + if let icon = item.icon { + let iconImage: UIImage? + switch icon { + case .qrCode: + iconImage = UIImage(bundleImageName: "Settings/QrIcon") + } + self.iconNode.image = generateTintedImage(image: iconImage, color: presentationData.theme.list.itemAccentColor) + self.iconNode.isHidden = false + self.iconButtonNode.isHidden = false + } else { + self.iconNode.isHidden = true + self.iconButtonNode.isHidden = true + } + let labelFrame = CGRect(origin: CGPoint(x: sideInset, y: 11.0), size: labelSize) let textFrame = CGRect(origin: CGPoint(x: sideInset, y: labelFrame.maxY + 3.0), size: textSize) @@ -294,6 +355,12 @@ private final class PeerInfoScreenLabeledValueItemNode: PeerInfoScreenItemNode { let height = labelSize.height + 3.0 + textSize.height + 22.0 + let iconButtonFrame = CGRect(x: width - safeInsets.right - height, y: 0.0, width: height, height: height) + transition.updateFrame(node: self.iconButtonNode, frame: iconButtonFrame) + if let iconSize = self.iconNode.image?.size { + transition.updateFrame(node: self.iconNode, frame: CGRect(origin: CGPoint(x: width - safeInsets.right - sideInset - iconSize.width + 5.0, y: floorToScreenPixels((height - iconSize.height) / 2.0)), size: iconSize)) + } + let highlightNodeOffset: CGFloat = topItem == nil ? 0.0 : UIScreenPixel self.selectionNode.update(size: CGSize(width: width, height: height + highlightNodeOffset), theme: presentationData.theme, transition: transition) transition.updateFrame(node: self.selectionNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -highlightNodeOffset), size: CGSize(width: width, height: height + highlightNodeOffset))) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 35069a39b9..ef25e82d2d 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -1011,6 +1011,9 @@ final class PeerInfoHeaderNavigationButton: HighlightableButtonNode { text = "" icon = PresentationResourcesRootController.navigationMoreCircledIcon(presentationData.theme) isGestureEnabled = true + case .qrCode: + text = "" + icon = PresentationResourcesRootController.navigationQrCodeIcon(presentationData.theme) } self.accessibilityLabel = text self.containerNode.isGestureEnabled = isGestureEnabled @@ -1059,6 +1062,7 @@ enum PeerInfoHeaderNavigationButtonKey { case editPhoto case editVideo case more + case qrCode } struct PeerInfoHeaderNavigationButtonSpec: Equatable { @@ -1068,14 +1072,19 @@ struct PeerInfoHeaderNavigationButtonSpec: Equatable { final class PeerInfoHeaderNavigationButtonContainerNode: ASDisplayNode { private var presentationData: PresentationData? - private(set) var buttonNodes: [PeerInfoHeaderNavigationButtonKey: PeerInfoHeaderNavigationButton] = [:] + private(set) var leftButtonNodes: [PeerInfoHeaderNavigationButtonKey: PeerInfoHeaderNavigationButton] = [:] + private(set) var rightButtonNodes: [PeerInfoHeaderNavigationButtonKey: PeerInfoHeaderNavigationButton] = [:] - private var currentButtons: [PeerInfoHeaderNavigationButtonSpec] = [] + private var currentLeftButtons: [PeerInfoHeaderNavigationButtonSpec] = [] + private var currentRightButtons: [PeerInfoHeaderNavigationButtonSpec] = [] var isWhite: Bool = false { didSet { if self.isWhite != oldValue { - for (_, buttonNode) in self.buttonNodes { + for (_, buttonNode) in self.leftButtonNodes { + buttonNode.isWhite = self.isWhite + } + for (_, buttonNode) in self.rightButtonNodes { buttonNode.isWhite = self.isWhite } } @@ -1084,27 +1093,107 @@ final class PeerInfoHeaderNavigationButtonContainerNode: ASDisplayNode { var performAction: ((PeerInfoHeaderNavigationButtonKey, ContextReferenceContentNode?, ContextGesture?) -> Void)? - func update(size: CGSize, presentationData: PresentationData, buttons: [PeerInfoHeaderNavigationButtonSpec], expandFraction: CGFloat, transition: ContainedViewLayoutTransition) { + func update(size: CGSize, presentationData: PresentationData, leftButtons: [PeerInfoHeaderNavigationButtonSpec], rightButtons: [PeerInfoHeaderNavigationButtonSpec], expandFraction: CGFloat, transition: ContainedViewLayoutTransition) { let maximumExpandOffset: CGFloat = 14.0 let expandOffset: CGFloat = -expandFraction * maximumExpandOffset - if self.currentButtons != buttons || presentationData.strings !== self.presentationData?.strings { - self.currentButtons = buttons + + if self.currentLeftButtons != leftButtons || presentationData.strings !== self.presentationData?.strings { + self.currentLeftButtons = leftButtons - var nextRegularButtonOrigin = size.width - 16.0 - var nextExpandedButtonOrigin = size.width - 16.0 - for spec in buttons.reversed() { + var nextRegularButtonOrigin = 16.0 + var nextExpandedButtonOrigin = 16.0 + for spec in leftButtons.reversed() { let buttonNode: PeerInfoHeaderNavigationButton var wasAdded = false - if let current = self.buttonNodes[spec.key] { + if let current = self.leftButtonNodes[spec.key] { buttonNode = current } else { wasAdded = true buttonNode = PeerInfoHeaderNavigationButton() - self.buttonNodes[spec.key] = buttonNode + self.leftButtonNodes[spec.key] = buttonNode self.addSubnode(buttonNode) buttonNode.isWhite = self.isWhite buttonNode.action = { [weak self] _, gesture in - guard let strongSelf = self, let buttonNode = strongSelf.buttonNodes[spec.key] else { + guard let strongSelf = self, let buttonNode = strongSelf.leftButtonNodes[spec.key] else { + return + } + strongSelf.performAction?(spec.key, buttonNode.contextSourceNode, gesture) + } + } + let buttonSize = buttonNode.update(key: spec.key, presentationData: presentationData, height: size.height) + var nextButtonOrigin = spec.isForExpandedView ? nextExpandedButtonOrigin : nextRegularButtonOrigin + let buttonFrame = CGRect(origin: CGPoint(x: nextButtonOrigin, y: expandOffset + (spec.isForExpandedView ? maximumExpandOffset : 0.0)), size: buttonSize) + nextButtonOrigin += buttonSize.width + 4.0 + if spec.isForExpandedView { + nextExpandedButtonOrigin = nextButtonOrigin + } else { + nextRegularButtonOrigin = nextButtonOrigin + } + let alphaFactor: CGFloat = spec.isForExpandedView ? expandFraction : (1.0 - expandFraction) + if wasAdded { + buttonNode.frame = buttonFrame + buttonNode.alpha = 0.0 + transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) + } else { + transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame) + transition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) + } + } + var removeKeys: [PeerInfoHeaderNavigationButtonKey] = [] + for (key, _) in self.leftButtonNodes { + if !rightButtons.contains(where: { $0.key == key }) { + removeKeys.append(key) + } + } + for key in removeKeys { + if let buttonNode = self.leftButtonNodes.removeValue(forKey: key) { + buttonNode.removeFromSupernode() + } + } + } else { + var nextRegularButtonOrigin = 16.0 + var nextExpandedButtonOrigin = 16.0 + for spec in rightButtons.reversed() { + if let buttonNode = self.leftButtonNodes[spec.key] { + let buttonSize = buttonNode.bounds.size + var nextButtonOrigin = spec.isForExpandedView ? nextExpandedButtonOrigin : nextRegularButtonOrigin + let buttonFrame = CGRect(origin: CGPoint(x: nextButtonOrigin, y: expandOffset + (spec.isForExpandedView ? maximumExpandOffset : 0.0)), size: buttonSize) + nextButtonOrigin += buttonSize.width + 4.0 + if spec.isForExpandedView { + nextExpandedButtonOrigin = nextButtonOrigin + } else { + nextRegularButtonOrigin = nextButtonOrigin + } + transition.updateFrameAdditiveToCenter(node: buttonNode, frame: buttonFrame) + let alphaFactor: CGFloat = spec.isForExpandedView ? expandFraction : (1.0 - expandFraction) + + var buttonTransition = transition + if case let .animated(duration, curve) = buttonTransition, alphaFactor == 0.0 { + buttonTransition = .animated(duration: duration * 0.25, curve: curve) + } + buttonTransition.updateAlpha(node: buttonNode, alpha: alphaFactor * alphaFactor) + } + } + } + + if self.currentRightButtons != rightButtons || presentationData.strings !== self.presentationData?.strings { + self.currentRightButtons = rightButtons + + var nextRegularButtonOrigin = size.width - 16.0 + var nextExpandedButtonOrigin = size.width - 16.0 + for spec in rightButtons.reversed() { + let buttonNode: PeerInfoHeaderNavigationButton + var wasAdded = false + if let current = self.rightButtonNodes[spec.key] { + buttonNode = current + } else { + wasAdded = true + buttonNode = PeerInfoHeaderNavigationButton() + self.rightButtonNodes[spec.key] = buttonNode + self.addSubnode(buttonNode) + buttonNode.isWhite = self.isWhite + buttonNode.action = { [weak self] _, gesture in + guard let strongSelf = self, let buttonNode = strongSelf.rightButtonNodes[spec.key] else { return } strongSelf.performAction?(spec.key, buttonNode.contextSourceNode, gesture) @@ -1130,21 +1219,21 @@ final class PeerInfoHeaderNavigationButtonContainerNode: ASDisplayNode { } } var removeKeys: [PeerInfoHeaderNavigationButtonKey] = [] - for (key, _) in self.buttonNodes { - if !buttons.contains(where: { $0.key == key }) { + for (key, _) in self.rightButtonNodes { + if !rightButtons.contains(where: { $0.key == key }) { removeKeys.append(key) } } for key in removeKeys { - if let buttonNode = self.buttonNodes.removeValue(forKey: key) { + if let buttonNode = self.rightButtonNodes.removeValue(forKey: key) { buttonNode.removeFromSupernode() } } } else { var nextRegularButtonOrigin = size.width - 16.0 var nextExpandedButtonOrigin = size.width - 16.0 - for spec in buttons.reversed() { - if let buttonNode = self.buttonNodes[spec.key] { + for spec in rightButtons.reversed() { + if let buttonNode = self.rightButtonNodes[spec.key] { let buttonSize = buttonNode.bounds.size var nextButtonOrigin = spec.isForExpandedView ? nextExpandedButtonOrigin : nextRegularButtonOrigin let buttonFrame = CGRect(origin: CGPoint(x: nextButtonOrigin - buttonSize.width, y: expandOffset + (spec.isForExpandedView ? maximumExpandOffset : 0.0)), size: buttonSize) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 446dd20d5a..6f8ed8dd81 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -62,6 +62,7 @@ import PeerInfoAvatarListNode import PasswordSetupUI import CalendarMessageScreen import TooltipUI +import QrCodeUI protocol PeerInfoScreenItem: AnyObject { var id: AnyHashable { get } @@ -462,6 +463,7 @@ private final class PeerInfoInteraction { let openDeletePeer: () -> Void let openFaq: (String?) -> Void let openAddMember: () -> Void + let openQrCode: () -> Void init( openUsername: @escaping (String) -> Void, @@ -501,7 +503,8 @@ private final class PeerInfoInteraction { updateBio: @escaping (String) -> Void, openDeletePeer: @escaping () -> Void, openFaq: @escaping (String?) -> Void, - openAddMember: @escaping () -> Void + openAddMember: @escaping () -> Void, + openQrCode: @escaping () -> Void ) { self.openUsername = openUsername self.openPhone = openPhone @@ -541,6 +544,7 @@ private final class PeerInfoInteraction { self.openDeletePeer = openDeletePeer self.openFaq = openFaq self.openAddMember = openAddMember + self.openQrCode = openQrCode } } @@ -854,10 +858,12 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese })) } if let username = user.username { - items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 1, label: presentationData.strings.Profile_Username, text: "@\(username)", textColor: .accent, action: { + items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: 1, label: presentationData.strings.Profile_Username, text: "@\(username)", textColor: .accent, icon: .qrCode, action: { interaction.openUsername(username) }, longTapAction: { sourceNode in interaction.openPeerInfoContextMenu(.link, sourceNode) + }, iconAction: { + interaction.openQrCode() }, requestLayout: { interaction.requestLayout() })) @@ -947,10 +953,12 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese } if let username = channel.username { - items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemUsername, label: presentationData.strings.Channel_LinkItem, text: "https://t.me/\(username)", textColor: .accent, action: { + items[.peerInfo]!.append(PeerInfoScreenLabeledValueItem(id: ItemUsername, label: presentationData.strings.Channel_LinkItem, text: "https://t.me/\(username)", textColor: .accent, icon: .qrCode, action: { interaction.openUsername(username) }, longTapAction: { sourceNode in interaction.openPeerInfoContextMenu(.link, sourceNode) + }, iconAction: { + interaction.openQrCode() }, requestLayout: { interaction.requestLayout() })) @@ -1666,6 +1674,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }, openAddMember: { [weak self] in self?.openAddMember() + }, + openQrCode: { [weak self] in + self?.openQrCode() } ) @@ -2743,6 +2754,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate if let source = source { strongSelf.displayMediaGalleryContextMenu(source: source, gesture: gesture) } + case .qrCode: + strongSelf.openQrCode() case .editPhoto, .editVideo: break } @@ -5377,6 +5390,13 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate presentAddMembers(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, parentController: controller, groupPeer: groupPeer, selectAddMemberDisposable: self.selectAddMemberDisposable, addMemberDisposable: self.addMemberDisposable) } + private func openQrCode() { + guard let data = self.data, let peer = data.peer, let controller = self.controller else { + return + } + controller.present(QrCodeScreen(context: self.context, updatedPresentationData: controller.updatedPresentationData, subject: .peer(peer: EnginePeer(peer))), in: .window(.root)) + } + fileprivate func openSettings(section: PeerInfoSettingsSection) { switch section { case .avatar: @@ -5935,7 +5955,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate private weak var mediaGalleryContextMenu: ContextController? func displaySharedMediaFastScrollingTooltip() { - guard let buttonNode = self.headerNode.navigationButtonContainer.buttonNodes[.more] else { + guard let buttonNode = self.headerNode.navigationButtonContainer.rightButtonNodes[.more] else { return } guard let controller = self.controller else { @@ -6613,23 +6633,27 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate transition.updateFrame(node: self.headerNode.navigationButtonContainer, frame: CGRect(origin: CGPoint(x: layout.safeInsets.left, y: layout.statusBarHeight ?? 0.0), size: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: navigationBarHeight))) self.headerNode.navigationButtonContainer.isWhite = self.headerNode.isAvatarExpanded - var navigationButtons: [PeerInfoHeaderNavigationButtonSpec] = [] + var leftNavigationButtons: [PeerInfoHeaderNavigationButtonSpec] = [] + var rightNavigationButtons: [PeerInfoHeaderNavigationButtonSpec] = [] if self.state.isEditing { - navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .done, isForExpandedView: false)) + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .done, isForExpandedView: false)) } else { if self.isSettings { - navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) - navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true)) + leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .qrCode, isForExpandedView: false)) + leftNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .qrCode, isForExpandedView: true)) + + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true)) } else if peerInfoCanEdit(peer: self.data?.peer, cachedData: self.data?.cachedData, isContact: self.data?.isContact) { - navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .edit, isForExpandedView: false)) } if self.state.selectedMessageIds == nil { if let currentPaneKey = self.paneContainerNode.currentPaneKey { switch currentPaneKey { case .files, .music, .links, .members: - navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true)) + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .search, isForExpandedView: true)) case .media: - navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true)) + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .more, isForExpandedView: true)) default: break } @@ -6642,10 +6666,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } } } else { - navigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .selectionDone, isForExpandedView: true)) + rightNavigationButtons.append(PeerInfoHeaderNavigationButtonSpec(key: .selectionDone, isForExpandedView: true)) } } - self.headerNode.navigationButtonContainer.update(size: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: navigationBarHeight), presentationData: self.presentationData, buttons: navigationButtons, expandFraction: effectiveAreaExpansionFraction, transition: transition) + self.headerNode.navigationButtonContainer.update(size: CGSize(width: layout.size.width - layout.safeInsets.left * 2.0, height: navigationBarHeight), presentationData: self.presentationData, leftButtons: leftNavigationButtons, rightButtons: rightNavigationButtons, expandFraction: effectiveAreaExpansionFraction, transition: transition) } }