diff --git a/submodules/AuthTransferUI/BUCK b/submodules/AuthTransferUI/BUCK index 494377d70f..c496f26116 100644 --- a/submodules/AuthTransferUI/BUCK +++ b/submodules/AuthTransferUI/BUCK @@ -9,6 +9,7 @@ static_library( "//submodules/TelegramCore:TelegramCore#shared", "//submodules/SyncCore:SyncCore#shared", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared", + "//submodules/AsyncDisplayKit:AsyncDisplayKit#shared", "//submodules/Display:Display#shared", "//submodules/TelegramPresentationData:TelegramPresentationData", "//submodules/AccountContext:AccountContext", @@ -16,6 +17,8 @@ static_library( "//submodules/Camera:Camera", "//submodules/GlassButtonNode:GlassButtonNode", "//submodules/AlertUI:AlertUI", + "//submodules/AppBundle:AppBundle", + "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", ], frameworks = [ "$SDKROOT/System/Library/Frameworks/Foundation.framework", diff --git a/submodules/AuthTransferUI/Sources/AuthTransferConfirmationScreen.swift b/submodules/AuthTransferUI/Sources/AuthTransferConfirmationScreen.swift new file mode 100644 index 0000000000..8282fc23a2 --- /dev/null +++ b/submodules/AuthTransferUI/Sources/AuthTransferConfirmationScreen.swift @@ -0,0 +1,166 @@ +import Foundation +import UIKit +import AppBundle +import AsyncDisplayKit +import Display +import SolidRoundedButtonNode +import SwiftSignalKit +import OverlayStatusController +import AnimatedStickerNode +import TelegramPresentationData +import TelegramCore +import AccountContext + +final class AuthTransferConfirmationNode: ASDisplayNode { + private let context: AccountContext + private var presentationData: PresentationData + private let tokenInfo: AuthTransferTokenInfo + + private let containerNode: ASDisplayNode + private let backgroundNode: ASImageNode + private let iconNode: ASImageNode + private let titleNode: ImmediateTextNode + private let appNameNode: ImmediateTextNode + private let locationInfoNode: ImmediateTextNode + private let acceptButtonNode: SolidRoundedButtonNode + private let cancelButtonNode: SolidRoundedButtonNode + + private var validLayout: (ContainerViewLayout, CGFloat)? + + init(context: AccountContext, presentationData: PresentationData, tokenInfo: AuthTransferTokenInfo, accept: @escaping () -> Void, cancel: @escaping () -> Void) { + self.context = context + self.presentationData = presentationData + self.tokenInfo = tokenInfo + + self.containerNode = ASDisplayNode() + + self.backgroundNode = ASImageNode() + self.backgroundNode.displayWithoutProcessing = true + self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 24.0, color: self.presentationData.theme.list.plainBackgroundColor) + + self.iconNode = ASImageNode() + self.iconNode.displayWithoutProcessing = true + self.iconNode.displaysAsynchronously = false + self.iconNode.image = UIImage(bundleImageName: "Settings/TransferAuthLaptop") + + self.titleNode = ImmediateTextNode() + self.titleNode.textAlignment = .center + self.titleNode.maximumNumberOfLines = 2 + + self.appNameNode = ImmediateTextNode() + self.appNameNode.textAlignment = .center + self.appNameNode.maximumNumberOfLines = 2 + + self.locationInfoNode = ImmediateTextNode() + self.locationInfoNode.textAlignment = .center + self.locationInfoNode.maximumNumberOfLines = 0 + + self.acceptButtonNode = SolidRoundedButtonNode(title: "Confirm Log In", icon: nil, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemDestructiveColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 10.0, gloss: false) + self.cancelButtonNode = SolidRoundedButtonNode(title: self.presentationData.strings.Common_Cancel, icon: nil, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 10.0, gloss: false) + + super.init() + + self.addSubnode(self.containerNode) + self.containerNode.addSubnode(self.backgroundNode) + self.containerNode.addSubnode(self.iconNode) + self.containerNode.addSubnode(self.titleNode) + self.containerNode.addSubnode(self.appNameNode) + self.containerNode.addSubnode(self.locationInfoNode) + self.containerNode.addSubnode(self.acceptButtonNode) + self.containerNode.addSubnode(self.cancelButtonNode) + + let titleFont = Font.bold(24.0) + let subtitleFont = Font.regular(16.0) + let textColor = self.presentationData.theme.list.itemPrimaryTextColor + let seccondaryTextColor = self.presentationData.theme.list.itemSecondaryTextColor + + self.titleNode.attributedText = NSAttributedString(string: "\(tokenInfo.appName)", font: titleFont, textColor: textColor) + + self.appNameNode.attributedText = NSAttributedString(string: "\(tokenInfo.deviceModel), \(tokenInfo.platform) \(tokenInfo.systemVersion)", font: subtitleFont, textColor: seccondaryTextColor) + + self.locationInfoNode.attributedText = NSAttributedString(string: "\(tokenInfo.region)\nIP: \(tokenInfo.ip)", font: subtitleFont, textColor: seccondaryTextColor) + + self.acceptButtonNode.pressed = { [weak self] in + accept() + } + self.cancelButtonNode.pressed = { + cancel() + } + } + + override func didLoad() { + super.didLoad() + } + + func animateIn() { + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: self.containerNode.bounds.height), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } + + func animateOut(completion: @escaping () -> Void) { + self.containerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: self.containerNode.bounds.height), duration: 0.3, removeOnCompletion: false, additive: true, completion: { _ in + completion() + }) + } + + func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + var insets = layout.insets(options: []) + let sideInset: CGFloat = 22.0 + + let buttonSideInset: CGFloat = 16.0 + let bottomInset = insets.bottom + 10.0 + let buttonWidth = layout.size.width - buttonSideInset * 2.0 + let buttonHeight: CGFloat = 50.0 + let buttonSpacing: CGFloat = 20.0 + let contentButtonSpacing: CGFloat = 35.0 + let titleSpacing: CGFloat = 1.0 + let locationSpacing: CGFloat = 35.0 + let iconSpacing: CGFloat = 35.0 + let topInset: CGFloat = 35.0 + + let iconSize = self.iconNode.image?.size ?? CGSize(width: 10.0, height: 1.0) + let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: .greatestFiniteMagnitude)) + let appNameSize = self.appNameNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: .greatestFiniteMagnitude)) + let locationSize = self.locationInfoNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: .greatestFiniteMagnitude)) + + var contentHeight: CGFloat = 0.0 + contentHeight += topInset + iconSize.height + contentHeight += iconSpacing + titleSize.height + contentHeight += titleSpacing + appNameSize.height + contentHeight += locationSpacing + locationSize.height + contentHeight += contentButtonSpacing + bottomInset + buttonHeight + buttonSpacing + buttonHeight + + let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: topInset), size: iconSize) + transition.updateFrame(node: self.iconNode, frame: iconFrame) + + let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleSize) + transition.updateFrame(node: self.titleNode, frame: titleFrame) + + let appNameFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - appNameSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: appNameSize) + transition.updateFrame(node: self.appNameNode, frame: appNameFrame) + + let locationFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - locationSize.width) / 2.0), y: appNameFrame.maxY + locationSpacing), size: locationSize) + transition.updateFrame(node: self.locationInfoNode, frame: locationFrame) + + let cancelButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: contentHeight - bottomInset - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight)) + transition.updateFrame(node: self.cancelButtonNode, frame: cancelButtonFrame) + self.cancelButtonNode.updateLayout(width: cancelButtonFrame.width, transition: transition) + + let acceptButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: cancelButtonFrame.minY - buttonSpacing - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight)) + transition.updateFrame(node: self.acceptButtonNode, frame: acceptButtonFrame) + self.acceptButtonNode.updateLayout(width: acceptButtonFrame.width, transition: transition) + + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - contentHeight), size: CGSize(width: layout.size.width, height: contentHeight))) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: contentHeight + 24.0))) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let result = self.cancelButtonNode.view.hitTest(self.view.convert(point, to: self.cancelButtonNode.view), with: event) { + return result + } + if let result = self.acceptButtonNode.view.hitTest(self.view.convert(point, to: self.acceptButtonNode.view), with: event) { + return result + } + return super.hitTest(point, with: event) + } +} diff --git a/submodules/AuthTransferUI/Sources/AuthTransferScanScreen.swift b/submodules/AuthTransferUI/Sources/AuthTransferScanScreen.swift index 05bf3e34ba..7ac48824ca 100644 --- a/submodules/AuthTransferUI/Sources/AuthTransferScanScreen.swift +++ b/submodules/AuthTransferUI/Sources/AuthTransferScanScreen.swift @@ -136,16 +136,34 @@ public final class AuthTransferScanScreen: ViewController { return } if let url = URL(string: code), let parsedToken = parseAuthTransferUrl(url) { - print("import token: \(parsedToken.base64EncodedString())") - - strongSelf.approveDisposable.set((approveAuthTransferToken(account: strongSelf.context.account, token: parsedToken) - |> deliverOnMainQueue).start(error: { _ in - }, completed: { + let _ = (getAuthTransferTokenInfo(network: strongSelf.context.account.network, token: parsedToken) + |> deliverOnMainQueue).start(next: { tokenInfo in guard let strongSelf = self else { return } - strongSelf.dismiss() - })) + (strongSelf.displayNode as! AuthTransferScanScreenNode).updateTokenPreview(confirmationNode: AuthTransferConfirmationNode(context: strongSelf.context, presentationData: strongSelf.presentationData, tokenInfo: tokenInfo, accept: { + guard let strongSelf = self else { + return + } + strongSelf.approveDisposable.set((approveAuthTransferToken(account: strongSelf.context.account, token: parsedToken) + |> deliverOnMainQueue).start(error: { _ in + guard let strongSelf = self else { + return + } + (strongSelf.displayNode as! AuthTransferScanScreenNode).updateTokenPreview(confirmationNode: nil) + }, completed: { + guard let strongSelf = self else { + return + } + strongSelf.dismiss() + })) + }, cancel: { + guard let strongSelf = self else { + return + } + (strongSelf.displayNode as! AuthTransferScanScreenNode).updateTokenPreview(confirmationNode: nil) + })) + }) } }) } @@ -166,9 +184,11 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr private let bottomDimNode: ASDisplayNode private let leftDimNode: ASDisplayNode private let rightDimNode: ASDisplayNode + private let centerDimNode: ASDisplayNode private let frameNode: ASImageNode private let torchButtonNode: GlassButtonNode private let titleNode: ImmediateTextNode + private let textNode: ImmediateTextNode private let camera: Camera private let codeDisposable = MetaDisposable() @@ -176,6 +196,8 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr fileprivate let focusedCode = ValuePromise(ignoreRepeated: true) private var focusedRect: CGRect? + private(set) var confirmationNode: AuthTransferConfirmationNode? + private var validLayout: (ContainerViewLayout, CGFloat)? init(presentationData: PresentationData) { @@ -204,6 +226,10 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr self.rightDimNode.alpha = 0.625 self.rightDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8) + self.centerDimNode = ASDisplayNode() + self.centerDimNode.alpha = 0.0 + self.centerDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8) + self.frameNode = ASImageNode() self.frameNode.image = generateFrameImage() @@ -215,6 +241,12 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr self.titleNode.maximumNumberOfLines = 0 self.titleNode.textAlignment = .center + self.textNode = ImmediateTextNode() + self.textNode.displaysAsynchronously = false + self.textNode.attributedText = NSAttributedString(string: "Scan a QR code to log into\nthis account on another device.", font: Font.regular(16.0), textColor: .white) + self.textNode.maximumNumberOfLines = 0 + self.textNode.textAlignment = .center + self.camera = Camera(configuration: .init(preset: .hd1920x1080, position: .back, audio: false)) super.init() @@ -227,9 +259,11 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr self.addSubnode(self.bottomDimNode) self.addSubnode(self.leftDimNode) self.addSubnode(self.rightDimNode) + self.addSubnode(self.centerDimNode) self.addSubnode(self.frameNode) self.addSubnode(self.torchButtonNode) self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) self.torchButtonNode.addTarget(self, action: #selector(self.torchPressed), forControlEvents: .touchUpInside) } @@ -281,6 +315,28 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr } } + func updateTokenPreview(confirmationNode: AuthTransferConfirmationNode?) { + if let confirmationNode = self.confirmationNode { + confirmationNode.animateOut { [weak confirmationNode] in + confirmationNode?.removeFromSupernode() + } + self.confirmationNode = nil + } + self.confirmationNode = confirmationNode + if let confirmationNode = self.confirmationNode { + self.addSubnode(confirmationNode) + if let (layout, navigationHeight) = self.validLayout { + confirmationNode.updateLayout(layout: layout, transition: .immediate) + confirmationNode.animateIn() + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) + } + } else { + if let (layout, navigationHeight) = self.validLayout { + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) + } + } + } + func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.validLayout = (layout, navigationHeight) @@ -307,7 +363,21 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr let dimAlpha: CGFloat let dimRect: CGRect let controlsAlpha: CGFloat - if let focusedRect = self.focusedRect { + var centerDimAlpha: CGFloat = 0.0 + var frameAlpha: CGFloat = 1.0 + if self.confirmationNode != nil { + controlsAlpha = 0.0 + dimAlpha = 0.625 + centerDimAlpha = 0.625 + frameAlpha = 0.0 + if let focusedRect = self.focusedRect { + let side = max(bounds.width * focusedRect.width, bounds.height * focusedRect.height) * 0.6 + let center = CGPoint(x: (1.0 - focusedRect.center.y) * bounds.width, y: focusedRect.center.x * bounds.height) + dimRect = CGRect(x: center.x - side / 2.0, y: center.y - side / 2.0, width: side, height: side) + } else { + dimRect = CGRect(x: dimInset, y: dimHeight, width: layout.size.width - dimInset * 2.0, height: layout.size.height - dimHeight * 2.0) + } + } else if let focusedRect = self.focusedRect { controlsAlpha = 0.0 dimAlpha = 1.0 let side = max(bounds.width * focusedRect.width, bounds.height * focusedRect.height) * 0.6 @@ -323,22 +393,33 @@ private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScr transition.updateAlpha(node: self.bottomDimNode, alpha: dimAlpha) transition.updateAlpha(node: self.leftDimNode, alpha: dimAlpha) transition.updateAlpha(node: self.rightDimNode, alpha: dimAlpha) + transition.updateAlpha(node: self.centerDimNode, alpha: centerDimAlpha) + transition.updateAlpha(node: self.frameNode, alpha: frameAlpha) transition.updateFrame(node: self.topDimNode, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: dimRect.minY)) transition.updateFrame(node: self.bottomDimNode, frame: CGRect(x: 0.0, y: dimRect.maxY, width: layout.size.width, height: max(0.0, layout.size.height - dimRect.maxY))) transition.updateFrame(node: self.leftDimNode, frame: CGRect(x: 0.0, y: dimRect.minY, width: max(0.0, dimRect.minX), height: dimRect.height)) transition.updateFrame(node: self.rightDimNode, frame: CGRect(x: dimRect.maxX, y: dimRect.minY, width: max(0.0, layout.size.width - dimRect.maxX), height: dimRect.height)) transition.updateFrame(node: self.frameNode, frame: dimRect.insetBy(dx: -2.0, dy: -2.0)) + transition.updateFrame(node: self.centerDimNode, frame: dimRect) let buttonSize = CGSize(width: 72.0, height: 72.0) transition.updateFrame(node: self.torchButtonNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0), y: dimHeight + frameSide + 50.0), size: buttonSize)) transition.updateAlpha(node: self.titleNode, alpha: controlsAlpha) + transition.updateAlpha(node: self.textNode, alpha: controlsAlpha) transition.updateAlpha(node: self.torchButtonNode, alpha: controlsAlpha) let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height)) - let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: dimHeight - titleSize.height - titleSpacing), size: titleSize) + let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height)) + let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: dimHeight - textSize.height - titleSpacing), size: textSize) + let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: textFrame.minY - 18.0 - titleSize.height), size: titleSize) transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame) + transition.updateFrameAdditive(node: self.textNode, frame: textFrame) + + if let confirmationNode = self.confirmationNode { + confirmationNode.updateLayout(layout: layout, transition: transition) + } } @objc private func torchPressed() { diff --git a/submodules/SettingsUI/Sources/SettingsController.swift b/submodules/SettingsUI/Sources/SettingsController.swift index 5292743553..dcd5241e6d 100644 --- a/submodules/SettingsUI/Sources/SettingsController.swift +++ b/submodules/SettingsUI/Sources/SettingsController.swift @@ -113,7 +113,7 @@ private final class SettingsItemArguments { let keepPhone: () -> Void let openPhoneNumberChange: () -> Void let accountContextAction: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void - let openLoginDesktop: () -> Void + let openDevices: () -> Void init( accountManager: AccountManager, @@ -147,7 +147,7 @@ private final class SettingsItemArguments { keepPhone: @escaping () -> Void, openPhoneNumberChange: @escaping () -> Void, accountContextAction: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void, - openLoginDesktop: @escaping () -> Void + openDevices: @escaping () -> Void ) { self.accountManager = accountManager self.avatarAndNameInfoContext = avatarAndNameInfoContext @@ -180,7 +180,7 @@ private final class SettingsItemArguments { self.keepPhone = keepPhone self.openPhoneNumberChange = openPhoneNumberChange self.accountContextAction = accountContextAction - self.openLoginDesktop = openLoginDesktop + self.openDevices = openDevices } } @@ -189,7 +189,6 @@ private enum SettingsSection: Int32 { case phone case accounts case proxy - case loginDesktop case media case generalSettings case advanced @@ -210,7 +209,7 @@ private indirect enum SettingsEntry: ItemListNodeEntry { case proxy(PresentationTheme, UIImage?, String, String) - case loginDesktop(PresentationTheme, UIImage?, String) + case devices(PresentationTheme, UIImage?, String, String) case savedMessages(PresentationTheme, UIImage?, String) case recentCalls(PresentationTheme, UIImage?, String) @@ -238,8 +237,8 @@ private indirect enum SettingsEntry: ItemListNodeEntry { return SettingsSection.accounts.rawValue case .proxy: return SettingsSection.proxy.rawValue - case .loginDesktop: - return SettingsSection.loginDesktop.rawValue + case .devices: + return SettingsSection.media.rawValue case .savedMessages, .recentCalls, .stickers: return SettingsSection.media.rawValue case .notificationsAndSounds, .privacyAndSecurity, .dataAndStorage, .themes, .language: @@ -271,13 +270,13 @@ private indirect enum SettingsEntry: ItemListNodeEntry { return 1002 case .proxy: return 1003 - case .loginDesktop: - return 1004 case .savedMessages: - return 1005 + return 1004 case .recentCalls: - return 1006 + return 1005 case .stickers: + return 1006 + case .devices: return 1007 case .notificationsAndSounds: return 1008 @@ -390,8 +389,8 @@ private indirect enum SettingsEntry: ItemListNodeEntry { } else { return false } - case let .loginDesktop(lhsTheme, lhsImage, lhsText): - if case let .loginDesktop(rhsTheme, rhsImage, rhsText) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText { + case let .devices(lhsTheme, lhsImage, lhsText, lhsValue): + if case let .devices(rhsTheme, rhsImage, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsValue == rhsValue { return true } else { return false @@ -545,9 +544,9 @@ private indirect enum SettingsEntry: ItemListNodeEntry { return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: value, sectionId: ItemListSectionId(self.section), style: .blocks, action: { arguments.openProxy() }) - case let .loginDesktop(theme, image, text): - return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: { - arguments.openLoginDesktop() + case let .devices(theme, image, text, value): + return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: value, sectionId: ItemListSectionId(self.section), style: .blocks, action: { + arguments.openDevices() }) case let .savedMessages(theme, image, text): return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: { @@ -657,11 +656,10 @@ private func settingsEntries(account: Account, presentationData: PresentationDat entries.append(.proxy(presentationData.theme, PresentationResourcesSettings.proxy, presentationData.strings.Settings_Proxy, valueString)) } - entries.append(.loginDesktop(presentationData.theme, PresentationResourcesSettings.security, "Telegram Desktop")) - entries.append(.savedMessages(presentationData.theme, PresentationResourcesSettings.savedMessages, presentationData.strings.Settings_SavedMessages)) entries.append(.recentCalls(presentationData.theme, PresentationResourcesSettings.recentCalls, presentationData.strings.CallSettings_RecentCalls)) entries.append(.stickers(presentationData.theme, PresentationResourcesSettings.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks)) + entries.append(.devices(presentationData.theme, PresentationResourcesSettings.stickers, "Devices", "")) let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed) entries.append(.notificationsAndSounds(presentationData.theme, PresentationResourcesSettings.notifications, presentationData.strings.Settings_NotificationsAndSounds, notifyExceptions, notificationsWarning)) @@ -1068,7 +1066,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM } else { gesture?.cancel() } - }, openLoginDesktop: { + }, openDevices: { let _ = (contextValue.get() |> deliverOnMainQueue |> take(1)).start(next: { context in diff --git a/submodules/TelegramCore/Sources/Account.swift b/submodules/TelegramCore/Sources/Account.swift index f1fecc8907..55c03cea20 100644 --- a/submodules/TelegramCore/Sources/Account.swift +++ b/submodules/TelegramCore/Sources/Account.swift @@ -61,6 +61,12 @@ public class UnauthorizedAccount { public let testingEnvironment: Bool public let postbox: Postbox public let network: Network + private let stateManager: UnauthorizedAccountStateManager + + private let updateLoginTokenPipe = ValuePipe() + public var updateLoginTokenEvents: Signal { + return self.updateLoginTokenPipe.signal() + } public var masterDatacenterId: Int32 { return Int32(self.network.mtProto.datacenterId) @@ -76,6 +82,10 @@ public class UnauthorizedAccount { self.testingEnvironment = testingEnvironment self.postbox = postbox self.network = network + let updateLoginTokenPipe = self.updateLoginTokenPipe + self.stateManager = UnauthorizedAccountStateManager(network: network, updateLoginToken: { + updateLoginTokenPipe.putNext(Void()) + }) network.shouldKeepConnection.set(self.shouldBeServiceTaskMaster.get() |> map { mode -> Bool in @@ -99,6 +109,8 @@ public class UnauthorizedAccount { } network.context.beginExplicitBackupAddressDiscovery() }) + + self.stateManager.reset() } public func changedMasterDatacenterId(accountManager: AccountManager, masterDatacenterId: Int32) -> Signal { diff --git a/submodules/TelegramCore/Sources/UnauthorizedAccountStateManager.swift b/submodules/TelegramCore/Sources/UnauthorizedAccountStateManager.swift new file mode 100644 index 0000000000..19a87d91f3 --- /dev/null +++ b/submodules/TelegramCore/Sources/UnauthorizedAccountStateManager.swift @@ -0,0 +1,86 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi +import MtProtoKit +import SyncCore + +private final class UnauthorizedUpdateMessageService: NSObject, MTMessageService { + let pipe: ValuePipe<[Api.Update]> = ValuePipe() + var mtProto: MTProto? + + override init() { + super.init() + } + + func mtProtoWillAdd(_ mtProto: MTProto!) { + self.mtProto = mtProto + } + + func mtProtoDidChangeSession(_ mtProto: MTProto!) { + } + + func mtProtoServerDidChangeSession(_ mtProto: MTProto!, firstValidMessageId: Int64, otherValidMessageIds: [Any]!) { + } + + func putNext(_ updates: [Api.Update]) { + self.pipe.putNext(updates) + } + + func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!) { + if let updates = (message.body as? BoxedMessage)?.body as? Api.Updates { + self.addUpdates(updates) + } + } + + func addUpdates(_ updates: Api.Updates) { + switch updates { + case let .updates(updates, _, _, _, _): + self.putNext(updates) + case let .updatesCombined(updates, _, _, _, _, _): + self.putNext(updates) + case let .updateShort(update, _): + self.putNext([update]) + case .updateShortChatMessage, .updateShortMessage, .updatesTooLong, .updateShortSentMessage: + break + } + } +} + + +final class UnauthorizedAccountStateManager { + private let queue = Queue() + private let network: Network + private var updateService: UnauthorizedUpdateMessageService? + private let updateServiceDisposable = MetaDisposable() + private let updateLoginToken: () -> Void + + init(network: Network, updateLoginToken: @escaping () -> Void) { + self.network = network + self.updateLoginToken = updateLoginToken + } + + deinit { + self.updateServiceDisposable.dispose() + } + + func reset() { + self.queue.async { + if self.updateService == nil { + self.updateService = UnauthorizedUpdateMessageService() + let updateLoginToken = self.updateLoginToken + self.updateServiceDisposable.set(self.updateService!.pipe.signal().start(next: { updates in + for update in updates { + switch update { + case .updateLoginToken: + updateLoginToken() + default: + break + } + } + })) + self.network.mtProto.add(self.updateService) + } + } + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/TransferAuthLaptop.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/TransferAuthLaptop.imageset/Contents.json new file mode 100644 index 0000000000..c55acc952e --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/TransferAuthLaptop.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "EmojiComputer.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/submodules/TelegramUI/Images.xcassets/Settings/TransferAuthLaptop.imageset/EmojiComputer.pdf b/submodules/TelegramUI/Images.xcassets/Settings/TransferAuthLaptop.imageset/EmojiComputer.pdf new file mode 100644 index 0000000000..1bf7b79ae0 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Settings/TransferAuthLaptop.imageset/EmojiComputer.pdf differ diff --git a/submodules/TelegramUI/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift b/submodules/TelegramUI/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift index 0aec588b88..54bd615a18 100644 --- a/submodules/TelegramUI/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/AuthorizationSequencePhoneEntryControllerNode.swift @@ -217,6 +217,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { private var qrNode: ASImageNode? private let exportTokenDisposable = MetaDisposable() + private let tokenEventsDisposable = MetaDisposable() var accountUpdated: ((UnauthorizedAccount) -> Void)? private let debugAction: () -> Void @@ -297,10 +298,16 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { self.phoneAndCountryNode.checkPhone = { [weak self] in self?.checkPhone?() } + + self.tokenEventsDisposable.set((account.updateLoginTokenEvents + |> deliverOnMainQueue).start(next: { [weak self] _ in + self?.refreshQrToken() + })) } deinit { self.exportTokenDisposable.dispose() + self.tokenEventsDisposable.dispose() } override func didLoad() { @@ -425,6 +432,10 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { strongSelf.exportTokenDisposable.set(nil) strongSelf.account = account strongSelf.accountUpdated?(account) + strongSelf.tokenEventsDisposable.set((account.updateLoginTokenEvents + |> deliverOnMainQueue).start(next: { _ in + self?.refreshQrToken() + })) strongSelf.refreshQrToken() case .loggedIn: strongSelf.exportTokenDisposable.set(nil)