diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index e9c3314484..48bdbde43d 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -7809,8 +7809,6 @@ Sorry for the inconvenience."; "Premium.Gift.Info" = "You can review the list of features and terms of use for Telegram Premium [here]()."; "Premium.Gift.GiftSubscription" = "Gift Subscription for %@"; -"Premium.Gift.PricePerMonth" = "%@ / month"; - "Premium.Gift.Months_1" = "%@ Month"; "Premium.Gift.Months_any" = "%@ Months"; @@ -7986,15 +7984,23 @@ Sorry for the inconvenience."; "Login.EnterCodeSMSTitle" = "Enter Code"; "Login.EnterCodeSMSText" = "We've sent and SMS with an activation code to your phone **%@**."; "Login.SendCodeAsSMS" = "Send the code as an SMS"; - "Login.EnterCodeTelegramTitle" = "Enter Code"; "Login.EnterCodeTelegramText" = "We've sent the code to the **Telegram app** for %@ on your other device."; - "Login.AddEmailTitle" = "Add Email"; "Login.AddEmailText" = "Please enter your valid email address to protect your account."; "Login.AddEmailPlaceholder" = "Enter your email"; - "Login.EnterCodeEmailTitle" = "Check Your Email"; "Login.EnterCodeEmailText" = "Please enter the code we have sent to your email %@."; - +"Login.EnterNewEmailTitle" = "Enter New Email"; +"Login.EnterCodeNewEmailTitle" = "Check Your New Email"; +"Login.EnterCodeNewEmailText" = "Please enter the code we have sent to your new email %@."; "Login.WrongCodeError" = "Wrong code, please try again."; + +"PrivacySettings.LoginEmail" = "Login Email"; + +"Conversation.EmojiCopied" = "Emoji copied to clipboard"; + +"Conversation.EmojiTooltip" = "Tap here to choose more emoji"; + +"Premium.PricePerMonth" = "%@/month"; +"Premium.PricePerYear" = "%@/year"; diff --git a/submodules/AccountContext/BUILD b/submodules/AccountContext/BUILD index b9c7a36100..9bcfd3c64b 100644 --- a/submodules/AccountContext/BUILD +++ b/submodules/AccountContext/BUILD @@ -23,6 +23,7 @@ swift_library( "//submodules/MeshAnimationCache:MeshAnimationCache", "//submodules/Utils/RangeSet:RangeSet", "//submodules/InAppPurchaseManager:InAppPurchaseManager", + "//submodules/TextFormat:TextFormat", ], visibility = [ "//visibility:public", diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index bf2c51d228..d8561667e4 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -752,6 +752,8 @@ public protocol SharedAccountContext: AnyObject { func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController + func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController + func navigateToCurrentCall() var hasOngoingCall: ValuePromise { get } var immediateHasOngoingCall: Bool { get } diff --git a/submodules/AccountContext/Sources/OpenChatMessage.swift b/submodules/AccountContext/Sources/OpenChatMessage.swift index b5eb81cad0..65403b14a1 100644 --- a/submodules/AccountContext/Sources/OpenChatMessage.swift +++ b/submodules/AccountContext/Sources/OpenChatMessage.swift @@ -7,6 +7,7 @@ import Display import AsyncDisplayKit import UniversalMediaPlayer import TelegramPresentationData +import TextFormat public enum ChatControllerInteractionOpenMessageMode { case `default` @@ -37,6 +38,7 @@ public final class OpenChatMessageParams { public let callPeer: (PeerId, Bool) -> Void public let enqueueMessage: (EnqueueMessage) -> Void public let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? + public let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? public let setupTemporaryHiddenMedia: (Signal, Int, Media) -> Void public let chatAvatarHiddenMedia: (Signal, Media) -> Void public let actionInteraction: GalleryControllerActionInteraction? @@ -64,6 +66,7 @@ public final class OpenChatMessageParams { callPeer: @escaping (PeerId, Bool) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, + sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, setupTemporaryHiddenMedia: @escaping (Signal, Int, Media) -> Void, chatAvatarHiddenMedia: @escaping (Signal, Media) -> Void, actionInteraction: GalleryControllerActionInteraction? = nil, @@ -90,6 +93,7 @@ public final class OpenChatMessageParams { self.callPeer = callPeer self.enqueueMessage = enqueueMessage self.sendSticker = sendSticker + self.sendEmoji = sendEmoji self.setupTemporaryHiddenMedia = setupTemporaryHiddenMedia self.chatAvatarHiddenMedia = chatAvatarHiddenMedia self.actionInteraction = actionInteraction diff --git a/submodules/AuthorizationUI/BUILD b/submodules/AuthorizationUI/BUILD index c3c89a9c0a..e03bd64443 100644 --- a/submodules/AuthorizationUI/BUILD +++ b/submodules/AuthorizationUI/BUILD @@ -12,10 +12,32 @@ swift_library( deps = [ "//submodules/AsyncDisplayKit:AsyncDisplayKit", "//submodules/TelegramCore:TelegramCore", + "//submodules/Postbox:Postbox", "//submodules/Display:Display", + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/TextFormat:TextFormat", "//submodules/Markdown:Markdown", "//submodules/TelegramPresentationData:TelegramPresentationData", + "//submodules/AccountContext:AccountContext", + "//submodules/CountrySelectionUI:CountrySelectionUI", + "//submodules/PhoneNumberFormat:PhoneNumberFormat", + "//submodules/LegacyComponents:LegacyComponents", + "//submodules/LegacyMediaPickerUI:LegacyMediaPickerUI", + "//submodules/PasswordSetupUI:PasswordSetupUI", + "//submodules/TelegramNotices:TelegramNotices", + "//submodules/ProgressNavigationButtonNode:ProgressNavigationButtonNode", + "//submodules/AnimatedStickerNode:AnimatedStickerNode", + "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", + "//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode", + "//submodules/ImageCompression:ImageCompression", + "//submodules/RMIntro:RMIntro", + "//submodules/QrCode:QrCode", + "//submodules/PhoneInputNode:PhoneInputNode", + "//submodules/CodeInputView:CodeInputView", + "//submodules/DebugSettingsUI:DebugSettingsUI", + "//submodules/InvisibleInkDustNode:InvisibleInkDustNode", + "//submodules/AuthorizationUtils:AuthorizationUtils", + "//submodules/ManagedAnimationNode:ManagedAnimationNode", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceAwaitingAccountResetController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceAwaitingAccountResetController.swift similarity index 100% rename from submodules/TelegramUI/Sources/AuthorizationSequenceAwaitingAccountResetController.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequenceAwaitingAccountResetController.swift diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceAwaitingAccountResetControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceAwaitingAccountResetControllerNode.swift similarity index 99% rename from submodules/TelegramUI/Sources/AuthorizationSequenceAwaitingAccountResetControllerNode.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequenceAwaitingAccountResetControllerNode.swift index 81fdb570f2..def523172c 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceAwaitingAccountResetControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceAwaitingAccountResetControllerNode.swift @@ -4,7 +4,7 @@ import AsyncDisplayKit import Display import SwiftSignalKit import TelegramPresentationData -import AuthorizationUI +import AuthorizationUtils private func timerValueString(days: Int32, hours: Int32, minutes: Int32, color: UIColor, strings: PresentationStrings) -> NSAttributedString { var daysString = "" diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift similarity index 78% rename from submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryController.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift index 0007c83c2e..fdd03d4aae 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryController.swift @@ -6,7 +6,7 @@ import TelegramCore import TelegramPresentationData import ProgressNavigationButtonNode -final class AuthorizationSequenceCodeEntryController: ViewController { +public final class AuthorizationSequenceCodeEntryController: ViewController { private var controllerNode: AuthorizationSequenceCodeEntryControllerNode { return self.displayNode as! AuthorizationSequenceCodeEntryControllerNode } @@ -15,20 +15,20 @@ final class AuthorizationSequenceCodeEntryController: ViewController { private let theme: PresentationTheme private let openUrl: (String) -> Void - var loginWithCode: ((String) -> Void)? - var signInWithApple: (() -> Void)? + public var loginWithCode: ((String) -> Void)? + public var signInWithApple: (() -> Void)? var reset: (() -> Void)? var requestNextOption: (() -> Void)? - var data: (String, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?)? + var data: (String, String?, SentAuthorizationCodeType, AuthorizationCodeNextType?, Int32?)? var termsOfService: (UnauthorizedAccountTermsOfService, Bool)? private let hapticFeedback = HapticFeedback() private var appleSignInAllowed = false - var inProgress: Bool = false { + public var inProgress: Bool = false { didSet { // if self.inProgress { // let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.theme.rootController.navigationBar.accentTextColor)) @@ -40,7 +40,7 @@ final class AuthorizationSequenceCodeEntryController: ViewController { } } - init(presentationData: PresentationData, openUrl: @escaping (String) -> Void, back: @escaping () -> Void) { + public init(presentationData: PresentationData, openUrl: @escaping (String) -> Void, back: @escaping () -> Void) { self.strings = presentationData.strings self.theme = presentationData.theme self.openUrl = openUrl @@ -96,16 +96,16 @@ final class AuthorizationSequenceCodeEntryController: ViewController { self?.navigationItem.rightBarButtonItem?.isEnabled = value } - if let (number, codeType, nextType, timeout) = self.data { + if let (number, email, codeType, nextType, timeout) = self.data { var appleSignInAllowed = false if case let .email(_, _, _, appleSignInAllowedValue, _) = codeType { appleSignInAllowed = appleSignInAllowedValue } - self.controllerNode.updateData(number: number, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed) + self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed) } } - override func viewDidAppear(_ animated: Bool) { + override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.controllerNode.activateInput() @@ -115,19 +115,19 @@ final class AuthorizationSequenceCodeEntryController: ViewController { self.controllerNode.resetCode() } - func animateSuccess() { + public func animateSuccess() { self.controllerNode.animateSuccess() } - func animateError(text: String) { + public func animateError(text: String) { self.hapticFeedback.error() self.controllerNode.animateError(text: text) } - func updateData(number: String, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) { + public func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) { self.termsOfService = termsOfService - if self.data?.0 != number || self.data?.1 != codeType || self.data?.2 != nextType || self.data?.3 != timeout { - self.data = (number, codeType, nextType, timeout) + if self.data?.0 != number || self.data?.1 != email || self.data?.2 != codeType || self.data?.3 != nextType || self.data?.4 != timeout { + self.data = (number, email, codeType, nextType, timeout) var appleSignInAllowed = false if case let .email(_, _, _, appleSignInAllowedValue, _) = codeType { @@ -135,20 +135,20 @@ final class AuthorizationSequenceCodeEntryController: ViewController { } if self.isNodeLoaded { - self.controllerNode.updateData(number: number, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed) + self.controllerNode.updateData(number: number, email: email, codeType: codeType, nextType: nextType, timeout: timeout, appleSignInAllowed: appleSignInAllowed) self.requestLayout(transition: .immediate) } } } - override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + 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) } - @objc func nextPressed() { - guard let (_, type, _, _) = self.data else { + @objc private func nextPressed() { + guard let (_, _, type, _, _) = self.data else { return } diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift similarity index 98% rename from submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift index bd8677a754..585a67fb78 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceCodeEntryControllerNode.swift @@ -6,7 +6,6 @@ import TelegramCore import SwiftSignalKit import TelegramPresentationData import TextFormat -import AuthorizationUI import AuthenticationServices import CodeInputView import PhoneNumberFormat @@ -14,6 +13,7 @@ import AnimatedStickerNode import TelegramAnimatedStickerNode import SolidRoundedButtonNode import InvisibleInkDustNode +import AuthorizationUtils final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextFieldDelegate { private let strings: PresentationStrings @@ -54,6 +54,8 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF } } + var email: String? + var currentCode: String { return self.codeInputView.text } @@ -202,9 +204,10 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF self.codeInputView.text = "" } - func updateData(number: String, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, appleSignInAllowed: Bool) { + func updateData(number: String, email: String?, codeType: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, appleSignInAllowed: Bool) { self.codeType = codeType self.phoneNumber = number + self.email = email var appleSignInAllowed = appleSignInAllowed if #available(iOS 13.0, *) { @@ -213,7 +216,7 @@ final class AuthorizationSequenceCodeEntryControllerNode: ASDisplayNode, UITextF } self.appleSignInAllowed = appleSignInAllowed - self.currentOptionNode.attributedText = authorizationCurrentOptionText(codeType, phoneNumber: self.phoneNumber, email: nil, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor) + self.currentOptionNode.attributedText = authorizationCurrentOptionText(codeType, phoneNumber: self.phoneNumber, email: self.email, strings: self.strings, primaryColor: self.theme.list.itemPrimaryTextColor, accentColor: self.theme.list.itemAccentColor) if case .missedCall = codeType { self.currentOptionInfoNode.attributedText = NSAttributedString(string: self.strings.Login_CodePhonePatternInfoText, font: Font.regular(17.0), textColor: self.theme.list.itemPrimaryTextColor, paragraphAlignment: .center) } else { diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift similarity index 98% rename from submodules/TelegramUI/Sources/AuthorizationSequenceController.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift index 0c86f9cbc6..ae6f333db1 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceController.swift @@ -12,7 +12,6 @@ import TelegramPresentationData import TextFormat import AccountContext import CountrySelectionUI -import SettingsUI import PhoneNumberFormat import LegacyComponents import LegacyMediaPickerUI @@ -247,7 +246,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail guard let strongSelf = self, let controller = controller else { return } - controller.present(proxySettingsController(accountManager: strongSelf.sharedContext.accountManager, postbox: strongSelf.account.postbox, network: strongSelf.account.network, mode: .modal, presentationData: strongSelf.sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: strongSelf.sharedContext.presentationData), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) + controller.present(strongSelf.sharedContext.makeProxySettingsController(sharedContext: strongSelf.sharedContext, account: strongSelf.account), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) })) } controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: actions), in: .window(.root)) @@ -260,11 +259,11 @@ public final class AuthorizationSequenceController: NavigationController, MFMail return controller } - private func codeEntryController(number: String, type: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) -> AuthorizationSequenceCodeEntryController { + private func codeEntryController(number: String, email: String?, type: SentAuthorizationCodeType, nextType: AuthorizationCodeNextType?, timeout: Int32?, termsOfService: (UnauthorizedAccountTermsOfService, Bool)?) -> AuthorizationSequenceCodeEntryController { var currentController: AuthorizationSequenceCodeEntryController? for c in self.viewControllers { if let c = c as? AuthorizationSequenceCodeEntryController { - if c.data?.1 == type { + if c.data?.2 == type { currentController = c } break @@ -502,11 +501,14 @@ public final class AuthorizationSequenceController: NavigationController, MFMail authorizationController.performRequests() } } - controller.updateData(number: formatPhoneNumber(number), codeType: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService) + controller.updateData(number: formatPhoneNumber(number), email: email, codeType: type, nextType: nextType, timeout: timeout, termsOfService: termsOfService) return controller } private var signInWithAppleSetup = false + private var appleSignInAllowed = false + private var currentEmail: String? + private func emailSetupController(number: String, appleSignInAllowed: Bool) -> AuthorizationSequenceEmailEntryController { var currentController: AuthorizationSequenceEmailEntryController? for c in self.viewControllers { @@ -519,7 +521,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail if let currentController = currentController { controller = currentController } else { - controller = AuthorizationSequenceEmailEntryController(presentationData: self.presentationData, back: { [weak self] in + controller = AuthorizationSequenceEmailEntryController(presentationData: self.presentationData, mode: .setup, back: { [weak self] in guard let strongSelf = self else { return } @@ -536,9 +538,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail controller?.inProgress = true strongSelf.actionDisposable.set((sendLoginEmailCode(account: strongSelf.account, email: email) - |> deliverOnMainQueue).start(next: { result in - controller?.inProgress = false - }, error: { error in + |> deliverOnMainQueue).start(error: { error in if let strongSelf = self, let controller = controller { controller.inProgress = false @@ -554,6 +554,12 @@ public final class AuthorizationSequenceController: NavigationController, MFMail controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) } + }, completed: { [weak self] in + controller?.inProgress = false + + if let strongSelf = self { + strongSelf.currentEmail = email + } })) } controller.signInWithApple = { [weak self] in @@ -1017,9 +1023,13 @@ public final class AuthorizationSequenceController: NavigationController, MFMail } controllers.append(self.phoneEntryController(countryCode: defaultCountryCode(), number: "", splashController: nil)) if case let .emailSetupRequired(appleSignInAllowed) = type { + self.appleSignInAllowed = appleSignInAllowed controllers.append(self.emailSetupController(number: number, appleSignInAllowed: appleSignInAllowed)) } else { - controllers.append(self.codeEntryController(number: number, type: type, nextType: nextType, timeout: timeout, termsOfService: nil)) + if let _ = self.currentEmail { + controllers.append(self.emailSetupController(number: number, appleSignInAllowed: self.appleSignInAllowed)) + } + controllers.append(self.codeEntryController(number: number, email: self.currentEmail, type: type, nextType: nextType, timeout: timeout, termsOfService: nil)) } self.setViewControllers(controllers, animated: !self.viewControllers.isEmpty) case let .passwordEntry(hint, _, _, suggestReset, syncContacts): diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceEmailEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryController.swift similarity index 73% rename from submodules/TelegramUI/Sources/AuthorizationSequenceEmailEntryController.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryController.swift index 383b317194..fe838c9e05 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceEmailEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryController.swift @@ -5,34 +5,42 @@ import AsyncDisplayKit import TelegramPresentationData import ProgressNavigationButtonNode -final class AuthorizationSequenceEmailEntryController: ViewController { +public final class AuthorizationSequenceEmailEntryController: ViewController { + public enum Mode { + case setup + case change + } + + private let mode: Mode + private var controllerNode: AuthorizationSequenceEmailEntryControllerNode { return self.displayNode as! AuthorizationSequenceEmailEntryControllerNode } private let presentationData: PresentationData - var proceedWithEmail: ((String) -> Void)? - var signInWithApple: (() -> Void)? + public var proceedWithEmail: ((String) -> Void)? + public var signInWithApple: (() -> Void)? private let hapticFeedback = HapticFeedback() private var appleSignInAllowed = false - var inProgress: Bool = false { + public var inProgress: Bool = false { didSet { - if self.inProgress { - let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.accentTextColor)) - self.navigationItem.rightBarButtonItem = item - } else { - self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed)) - } +// if self.inProgress { +// let item = UIBarButtonItem(customDisplayNode: ProgressNavigationButtonNode(color: self.presentationData.theme.rootController.navigationBar.accentTextColor)) +// self.navigationItem.rightBarButtonItem = item +// } else { +// self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed)) +// } self.controllerNode.inProgress = self.inProgress } } - init(presentationData: PresentationData, back: @escaping () -> Void) { + public init(presentationData: PresentationData, mode: Mode, back: @escaping () -> Void) { self.presentationData = presentationData + self.mode = mode super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: AuthorizationSequenceController.navigationBarTheme(presentationData.theme), strings: NavigationBarStrings(presentationStrings: presentationData.strings))) @@ -57,7 +65,7 @@ final class AuthorizationSequenceEmailEntryController: ViewController { } override public func loadDisplayNode() { - self.displayNode = AuthorizationSequenceEmailEntryControllerNode(strings: self.presentationData.strings, theme: self.presentationData.theme) + self.displayNode = AuthorizationSequenceEmailEntryControllerNode(strings: self.presentationData.strings, theme: self.presentationData.theme, mode: self.mode) self.displayNodeDidLoad() self.controllerNode.view.disableAutomaticKeyboardHandling = [.forward, .backward] @@ -73,13 +81,13 @@ final class AuthorizationSequenceEmailEntryController: ViewController { self.controllerNode.updateData(appleSignInAllowed: self.appleSignInAllowed) } - override func viewDidAppear(_ animated: Bool) { + override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.controllerNode.activateInput() } - func updateData(appleSignInAllowed: Bool) { + public func updateData(appleSignInAllowed: Bool) { var appleSignInAllowed = appleSignInAllowed if #available(iOS 13.0, *) { } else { @@ -93,13 +101,13 @@ final class AuthorizationSequenceEmailEntryController: ViewController { } } - override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + 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) } - @objc func nextPressed() { + @objc private func nextPressed() { if self.controllerNode.currentEmail.isEmpty { if self.appleSignInAllowed { self.signInWithApple?() diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift similarity index 90% rename from submodules/TelegramUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift index ba4d857088..e9b005f837 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceEmailEntryControllerNode.swift @@ -3,7 +3,7 @@ import UIKit import AsyncDisplayKit import Display import TelegramPresentationData -import AuthorizationUI +import AuthorizationUtils import AuthenticationServices import AnimatedStickerNode import TelegramAnimatedStickerNode @@ -50,6 +50,7 @@ final class AuthorizationDividerNode: ASDisplayNode { final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UITextFieldDelegate { private let strings: PresentationStrings private let theme: PresentationTheme + private let mode: AuthorizationSequenceEmailEntryController.Mode private let animationNode: AnimatedStickerNode private let titleNode: ASTextNode @@ -79,9 +80,10 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText } } - init(strings: PresentationStrings, theme: PresentationTheme) { + init(strings: PresentationStrings, theme: PresentationTheme, mode: AuthorizationSequenceEmailEntryController.Mode) { self.strings = strings self.theme = theme + self.mode = mode self.animationNode = DefaultAnimatedStickerNodeImpl() self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "IntroMail"), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) @@ -90,7 +92,6 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText self.titleNode = ASTextNode() self.titleNode.isUserInteractionEnabled = false self.titleNode.displaysAsynchronously = false - self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_AddEmailTitle, font: Font.light(30.0), textColor: self.theme.list.itemPrimaryTextColor) self.noticeNode = ASTextNode() self.noticeNode.isUserInteractionEnabled = false @@ -190,10 +191,10 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText insets.top = layout.statusBarHeight ?? 20.0 if let inputHeight = layout.inputHeight { - insets.bottom += max(inputHeight, insets.bottom) + insets.bottom = max(inputHeight, insets.bottom) } - self.titleNode.attributedText = NSAttributedString(string: self.strings.Login_AddEmailTitle, font: Font.bold(28.0), textColor: self.theme.list.itemPrimaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: self.mode == .setup ? self.strings.Login_AddEmailTitle : self.strings.Login_EnterNewEmailTitle, font: Font.bold(28.0), textColor: self.theme.list.itemPrimaryTextColor) let animationSize = CGSize(width: 100.0, height: 100.0) let titleSize = self.titleNode.measure(CGSize(width: layout.size.width, height: CGFloat.greatestFiniteMagnitude)) @@ -209,19 +210,22 @@ final class AuthorizationSequenceEmailEntryControllerNode: ASDisplayNode, UIText items.append(AuthorizationLayoutItem(node: self.titleNode, size: titleSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.noticeNode, size: noticeSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 18.0, maxValue: 18.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) - items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 32.0, maxValue: 60.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + items.append(AuthorizationLayoutItem(node: self.codeField, size: CGSize(width: layout.size.width - 88.0, height: 44.0), spacingBefore: AuthorizationLayoutItemSpacing(weight: 22.0, maxValue: 40.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) items.append(AuthorizationLayoutItem(node: self.codeSeparatorNode, size: CGSize(width: layout.size.width - 48.0, height: UIScreenPixel), spacingBefore: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) + let inset: CGFloat = 24.0 + let buttonFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - proceedSize.width) / 2.0), y: layout.size.height - insets.bottom - proceedSize.height - inset), size: proceedSize) + transition.updateFrame(node: self.proceedNode, frame: buttonFrame) + + let dividerSize = self.dividerNode.updateLayout(width: layout.size.width) + transition.updateFrame(node: self.dividerNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - dividerSize.width) / 2.0), y: buttonFrame.minY - dividerSize.height), size: dividerSize)) + if let _ = self.signInWithAppleButton, self.appleSignInAllowed { self.dividerNode.isHidden = false - let dividerSize = self.dividerNode.updateLayout(width: layout.size.width) - items.append(AuthorizationLayoutItem(node: self.dividerNode, size: dividerSize, spacingBefore: AuthorizationLayoutItemSpacing(weight: 48.0, maxValue: 100.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) } else { self.dividerNode.isHidden = true } - items.append(AuthorizationLayoutItem(node: self.proceedNode, size: proceedSize, spacingBefore: self.dividerNode.isHidden ? AuthorizationLayoutItemSpacing(weight: 48.0, maxValue: 100.0) : AuthorizationLayoutItemSpacing(weight: 10.0, maxValue: 10.0), spacingAfter: AuthorizationLayoutItemSpacing(weight: 0.0, maxValue: 0.0))) - let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - 80.0)), items: items, transition: transition, failIfDoesNotFit: false) if let signInWithAppleButton = self.signInWithAppleButton, self.appleSignInAllowed { diff --git a/submodules/TelegramUI/Sources/AuthorizationSequencePasswordEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryController.swift similarity index 100% rename from submodules/TelegramUI/Sources/AuthorizationSequencePasswordEntryController.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryController.swift diff --git a/submodules/TelegramUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift similarity index 99% rename from submodules/TelegramUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift index b1d2f1be83..f955e75a11 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordEntryControllerNode.swift @@ -3,7 +3,7 @@ import UIKit import AsyncDisplayKit import Display import TelegramPresentationData -import AuthorizationUI +import AuthorizationUtils final class AuthorizationSequencePasswordEntryControllerNode: ASDisplayNode, UITextFieldDelegate { private let strings: PresentationStrings diff --git a/submodules/TelegramUI/Sources/AuthorizationSequencePasswordRecoveryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordRecoveryController.swift similarity index 100% rename from submodules/TelegramUI/Sources/AuthorizationSequencePasswordRecoveryController.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordRecoveryController.swift diff --git a/submodules/TelegramUI/Sources/AuthorizationSequencePasswordRecoveryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordRecoveryControllerNode.swift similarity index 99% rename from submodules/TelegramUI/Sources/AuthorizationSequencePasswordRecoveryControllerNode.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordRecoveryControllerNode.swift index e4efca8e13..3b2ffda392 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequencePasswordRecoveryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePasswordRecoveryControllerNode.swift @@ -3,7 +3,7 @@ import UIKit import AsyncDisplayKit import Display import TelegramPresentationData -import AuthorizationUI +import AuthorizationUtils final class AuthorizationSequencePasswordRecoveryControllerNode: ASDisplayNode, UITextFieldDelegate { private let strings: PresentationStrings diff --git a/submodules/TelegramUI/Sources/AuthorizationSequencePhoneEntryController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift similarity index 84% rename from submodules/TelegramUI/Sources/AuthorizationSequencePhoneEntryController.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift index 735f5072b0..6427aa57fe 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequencePhoneEntryController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryController.swift @@ -9,7 +9,6 @@ import TelegramPresentationData import ProgressNavigationButtonNode import AccountContext import CountrySelectionUI -import SettingsUI import PhoneNumberFormat import DebugSettingsUI @@ -29,6 +28,18 @@ final class AuthorizationSequencePhoneEntryController: ViewController { private let back: () -> Void private var currentData: (Int32, String?, String)? + + var codeNode: ASDisplayNode { + return self.controllerNode.codeNode + } + + var numberNode: ASDisplayNode { + return self.controllerNode.numberNode + } + + var buttonNode: ASDisplayNode { + return self.controllerNode.buttonNode + } var inProgress: Bool = false { didSet { @@ -39,11 +50,14 @@ final class AuthorizationSequencePhoneEntryController: ViewController { // self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Next, style: .done, target: self, action: #selector(self.nextPressed)) // } self.controllerNode.inProgress = self.inProgress + self.confirmationController?.inProgress = self.inProgress } } var loginWithNumber: ((String, Bool) -> Void)? var accountUpdated: ((UnauthorizedAccount) -> Void)? + weak var confirmationController: PhoneConfirmationController? + private let termsDisposable = MetaDisposable() private let hapticFeedback = HapticFeedback() @@ -98,13 +112,13 @@ final class AuthorizationSequencePhoneEntryController: ViewController { } private var shouldAnimateIn = false - private var transitionInArguments: (buttonFrame: CGRect, animationSnapshot: UIView, textSnapshot: UIView)? + private var transitionInArguments: (buttonFrame: CGRect, buttonTitle: String, animationSnapshot: UIView, textSnapshot: UIView)? func animateWithSplashController(_ controller: AuthorizationSequenceSplashController) { self.shouldAnimateIn = true if let animationSnapshot = controller.animationSnapshot, let textSnapshot = controller.textSnaphot { - self.transitionInArguments = (controller.buttonFrame, animationSnapshot, textSnapshot) + self.transitionInArguments = (controller.buttonFrame, controller.buttonTitle, animationSnapshot, textSnapshot) } } @@ -163,8 +177,8 @@ final class AuthorizationSequencePhoneEntryController: ViewController { if self.shouldAnimateIn { self.animatingIn = true - if let (buttonFrame, animationSnapshot, textSnapshot) = self.transitionInArguments { - self.controllerNode.willAnimateIn(buttonFrame: buttonFrame, animationSnapshot: animationSnapshot, textSnapshot: textSnapshot) + if let (buttonFrame, buttonTitle, animationSnapshot, textSnapshot) = self.transitionInArguments { + self.controllerNode.willAnimateIn(buttonFrame: buttonFrame, buttonTitle: buttonTitle, animationSnapshot: animationSnapshot, textSnapshot: textSnapshot) } Queue.mainQueue().justDispatch { self.controllerNode.activateInput() @@ -182,15 +196,23 @@ final class AuthorizationSequencePhoneEntryController: ViewController { } } + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if let confirmationController = self.confirmationController { + confirmationController.transitionOut() + } + } + override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) if self.shouldAnimateIn, let inputHeight = layout.inputHeight, inputHeight > 0.0 { - if let (buttonFrame, animationSnapshot, textSnapshot) = self.transitionInArguments { + if let (buttonFrame, buttonTitle, animationSnapshot, textSnapshot) = self.transitionInArguments { self.shouldAnimateIn = false - self.controllerNode.animateIn(buttonFrame: buttonFrame, animationSnapshot: animationSnapshot, textSnapshot: textSnapshot) + self.controllerNode.animateIn(buttonFrame: buttonFrame, buttonTitle: buttonTitle, animationSnapshot: animationSnapshot, textSnapshot: textSnapshot) } } } @@ -217,14 +239,16 @@ final class AuthorizationSequencePhoneEntryController: ViewController { actions.append(TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})) self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Login_PhoneNumberAlreadyAuthorized, actions: actions), in: .window(.root)) } else { - var actions: [TextAlertAction] = [] - actions.append(TextAlertAction(type: .genericAction, title: self.presentationData.strings.Login_Edit, action: {})) - actions.append(TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Login_Yes, action: { [weak self] in + let (code, formattedNumber) = self.controllerNode.formattedCodeAndNumber + + let confirmationController = PhoneConfirmationController(theme: self.presentationData.theme, strings: self.presentationData.strings, code: code, number: formattedNumber, sourceController: self) + confirmationController.proceed = { [weak self] in if let strongSelf = self { strongSelf.loginWithNumber?(strongSelf.controllerNode.currentNumber, strongSelf.controllerNode.syncContacts) } - })) - self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: logInNumber, text: self.presentationData.strings.Login_PhoneNumberConfirmation, actions: actions), in: .window(.root)) + } + (self.navigationController as? NavigationController)?.presentOverlay(controller: confirmationController, inGlobal: true, blockInteraction: true) + self.confirmationController = confirmationController } } else { self.hapticFeedback.error() diff --git a/submodules/TelegramUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift similarity index 55% rename from submodules/TelegramUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift index fc1c653868..c1798516aa 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequencePhoneEntryControllerNode.swift @@ -6,7 +6,6 @@ import TelegramCore import TelegramPresentationData import PhoneInputNode import CountrySelectionUI -import AuthorizationUI import QrCode import SwiftSignalKit import Postbox @@ -14,6 +13,8 @@ import AccountContext import AnimatedStickerNode import TelegramAnimatedStickerNode import SolidRoundedButtonNode +import AuthorizationUtils +import ManagedAnimationNode private final class PhoneAndCountryNode: ASDisplayNode { let strings: PresentationStrings @@ -23,9 +24,13 @@ private final class PhoneAndCountryNode: ASDisplayNode { var selectCountryCode: (() -> Void)? var checkPhone: (() -> Void)? + var hasNumberUpdated: ((Bool) -> Void)? + var numberUpdated: (() -> Void)? var preferredCountryIdForCode: [String: String] = [:] + var hasCountry = false + init(strings: PresentationStrings, theme: PresentationTheme) { self.strings = strings @@ -122,6 +127,7 @@ private final class PhoneAndCountryNode: ASDisplayNode { let flagString = emojiFlagForISOCountryCode(country.id) let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(country.id, strings: strongSelf.strings) ?? country.name strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemAccentColor, for: []) + strongSelf.hasCountry = true let maskFont = Font.with(size: 20.0, design: .regular, traits: [.monospacedNumbers]) if let mask = AuthorizationSequenceCountrySelectionController.lookupPatternByNumber(number, preferredCountries: strongSelf.preferredCountryIdForCode).flatMap({ NSAttributedString(string: $0, font: maskFont, textColor: theme.list.itemPlaceholderTextColor) }) { @@ -140,6 +146,14 @@ private final class PhoneAndCountryNode: ASDisplayNode { self.phoneInputNode.numberTextUpdated = { [weak self] number in if let strongSelf = self { let _ = processNumberChange(strongSelf.phoneInputNode.number) + + strongSelf.numberUpdated?() + + if strongSelf.hasCountry { + strongSelf.hasNumberUpdated?(!strongSelf.phoneInputNode.codeAndNumber.2.isEmpty) + } else { + strongSelf.hasNumberUpdated?(false) + } } } @@ -149,11 +163,14 @@ private final class PhoneAndCountryNode: ASDisplayNode { strongSelf.preferredCountryIdForCode[code] = name } + strongSelf.numberUpdated?() + if processNumberChange(strongSelf.phoneInputNode.number) { } else if let code = Int(code), let name = name, let countryName = countryCodeAndIdToName[CountryCodeAndId(code: code, id: name)] { let flagString = emojiFlagForISOCountryCode(name) let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(name, strings: strongSelf.strings) ?? countryName strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemAccentColor, for: []) + strongSelf.hasCountry = true if strongSelf.phoneInputNode.mask == nil { strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor) @@ -162,15 +179,23 @@ private final class PhoneAndCountryNode: ASDisplayNode { let flagString = emojiFlagForISOCountryCode(countryId) let localizedName: String = AuthorizationSequenceCountrySelectionController.lookupCountryNameById(countryId, strings: strongSelf.strings) ?? countryName strongSelf.countryButton.setTitle("\(flagString) \(localizedName)", with: Font.regular(20.0), with: theme.list.itemAccentColor, for: []) + strongSelf.hasCountry = true if strongSelf.phoneInputNode.mask == nil { strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor) } } else { + strongSelf.hasCountry = false strongSelf.countryButton.setTitle(strings.Login_SelectCountry, with: Font.regular(20.0), with: theme.list.itemAccentColor, for: []) strongSelf.phoneInputNode.mask = nil strongSelf.phoneInputNode.numberField.textField.attributedPlaceholder = NSAttributedString(string: strings.Login_PhonePlaceholder, font: Font.regular(20.0), textColor: theme.list.itemPlaceholderTextColor) } + + if strongSelf.hasCountry { + strongSelf.hasNumberUpdated?(!strongSelf.phoneInputNode.codeAndNumber.2.isEmpty) + } else { + strongSelf.hasNumberUpdated?(false) + } } } @@ -253,6 +278,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { private let hasOtherAccounts: Bool private let animationNode: AnimatedStickerNode + private let managedAnimationNode: ManagedPhoneAnimationNode private let titleNode: ASTextNode private let noticeNode: ASTextNode private let phoneAndCountryNode: PhoneAndCountryNode @@ -278,6 +304,10 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { } } + var formattedCodeAndNumber: (String, String) { + return self.phoneAndCountryNode.phoneInputNode.formattedCodeAndNumber + } + var syncContacts: Bool { get { if self.hasOtherAccounts { @@ -307,6 +337,18 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { } } + var codeNode: ASDisplayNode { + return self.phoneAndCountryNode.phoneInputNode.countryCodeField + } + + var numberNode: ASDisplayNode { + return self.phoneAndCountryNode.phoneInputNode.numberField + } + + var buttonNode: ASDisplayNode { + return self.proceedNode + } + init(sharedContext: SharedAccountContext, account: UnauthorizedAccount, strings: PresentationStrings, theme: PresentationTheme, debugAction: @escaping () -> Void, hasOtherAccounts: Bool) { self.sharedContext = sharedContext self.account = account @@ -320,6 +362,9 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(name: "IntroPhone"), width: 256, height: 256, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) self.animationNode.visibility = true + self.managedAnimationNode = ManagedPhoneAnimationNode() + self.managedAnimationNode.isHidden = true + self.titleNode = ASTextNode() self.titleNode.isUserInteractionEnabled = true self.titleNode.displaysAsynchronously = false @@ -338,6 +383,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { self.proceedNode = SolidRoundedButtonNode(title: self.strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: self.theme), height: 50.0, cornerRadius: 11.0, gloss: false) self.proceedNode.progressType = .embedded + self.proceedNode.isEnabled = false super.init() @@ -353,6 +399,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { self.addSubnode(self.contactSyncNode) self.addSubnode(self.proceedNode) self.addSubnode(self.animationNode) + self.addSubnode(self.managedAnimationNode) self.contactSyncNode.isHidden = true self.phoneAndCountryNode.selectCountryCode = { [weak self] in @@ -361,6 +408,16 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { self.phoneAndCountryNode.checkPhone = { [weak self] in self?.checkPhone?() } + self.phoneAndCountryNode.hasNumberUpdated = { [weak self] hasNumber in + self?.proceedNode.isEnabled = hasNumber + } + self.phoneAndCountryNode.numberUpdated = { [weak self] in + if let strongSelf = self, !strongSelf.managedAnimationNode.isHidden { + if let state = ManagedPhoneAnimationState.allCases.randomElement() { + strongSelf.managedAnimationNode.enqueue(state) + } + } + } self.tokenEventsDisposable.set((account.updateLoginTokenEvents |> deliverOnMainQueue).start(next: { [weak self] _ in @@ -370,6 +427,11 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { self.proceedNode.pressed = { [weak self] in self?.checkPhone?() } + + self.animationNode.completed = { [weak self] _ in + self?.animationNode.isHidden = true + self?.managedAnimationNode.isHidden = false + } } deinit { @@ -390,9 +452,11 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { private var textSnapshotView: UIView? private var forcedButtonFrame: CGRect? - func willAnimateIn(buttonFrame: CGRect, animationSnapshot: UIView, textSnapshot: UIView) { + func willAnimateIn(buttonFrame: CGRect, buttonTitle: String, animationSnapshot: UIView, textSnapshot: UIView) { self.proceedNode.frame = buttonFrame - self.proceedNode.title = "Start Messaging" + + self.proceedNode.isEnabled = true + self.proceedNode.title = buttonTitle self.animationSnapshotView = animationSnapshot self.view.insertSubview(animationSnapshot, at: 0) @@ -413,7 +477,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { } } - func animateIn(buttonFrame: CGRect, animationSnapshot: UIView, textSnapshot: UIView) { + func animateIn(buttonFrame: CGRect, buttonTitle: String, animationSnapshot: UIView, textSnapshot: UIView) { self.proceedNode.animateTitle(to: self.strings.Login_Continue) let duration: Double = 0.3 @@ -423,6 +487,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { self?.animationSnapshotView = nil }) self.animationSnapshotView?.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -100.0), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) + self.animationSnapshotView?.layer.animateScale(from: 1.0, to: 0.3, duration: 0.4) self.textSnapshotView?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] _ in self?.textSnapshotView?.removeFromSuperview() @@ -437,6 +502,8 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { self.phoneAndCountryNode, self.contactSyncNode ] + + self.animationNode.layer.animateScale(from: 0.3, to: 1.0, duration: 0.3) for node in nodes { node.alpha = 1.0 @@ -492,6 +559,8 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { self.animationNode.updateLayout(size: animationSize) let _ = layoutAuthorizationItems(bounds: CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - insets.bottom - 80.0)), items: items, transition: transition, failIfDoesNotFit: false) + + transition.updateFrame(node: self.managedAnimationNode, frame: self.animationNode.frame) } func activateInput() { @@ -601,3 +670,366 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode { })) } } + +final class PhoneConfirmationController: ViewController { + private var controllerNode: Node { + return self.displayNode as! Node + } + + private let theme: PresentationTheme + private let strings: PresentationStrings + private let code: String + private let number: String + private weak var sourceController: AuthorizationSequencePhoneEntryController? + + var inProgress: Bool = false { + didSet { + if self.inProgress != oldValue { + if self.inProgress { + self.controllerNode.proceedNode.transitionToProgress() + } else { + self.controllerNode.proceedNode.transitionFromProgress() + } + } + } + } + + var proceed: () -> Void = {} + + class Node: ASDisplayNode { + private let dimNode: ASDisplayNode + private let backgroundNode: ASDisplayNode + + private let codeSourceNode: ImmediateTextNode + private let phoneSourceNode: ImmediateTextNode + + private let codeTargetNode: ImmediateTextNode + private let phoneTargetNode: ImmediateTextNode + + private let textNode: ImmediateTextNode + + private let cancelButton: HighlightableButtonNode + fileprivate let proceedNode: SolidRoundedButtonNode + + var proceed: () -> Void = {} + var cancel: () -> Void = {} + + init(theme: PresentationTheme, strings: PresentationStrings, code: String, number: String) { + self.dimNode = ASDisplayNode() + self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.4) + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.backgroundColor = theme.list.plainBackgroundColor + self.backgroundNode.cornerRadius = 11.0 + + self.textNode = ImmediateTextNode() + self.textNode.displaysAsynchronously = false + self.textNode.attributedText = NSAttributedString(string: strings.Login_PhoneNumberConfirmation, font: Font.regular(17.0), textColor: theme.list.itemPrimaryTextColor) + self.textNode.textAlignment = .center + + self.cancelButton = HighlightableButtonNode() + self.cancelButton.setTitle(strings.Login_Edit, with: Font.regular(19.0), with: theme.list.itemAccentColor, for: .normal) + + self.proceedNode = SolidRoundedButtonNode(title: strings.Login_Continue, theme: SolidRoundedButtonTheme(theme: theme), height: 50.0, cornerRadius: 11.0, gloss: false) + self.proceedNode.progressType = .embedded + + let font = Font.with(size: 20.0, design: .regular, traits: [.monospacedNumbers]) + let largeFont = Font.with(size: 34.0, design: .regular, weight: .bold, traits: [.monospacedNumbers]) + + self.codeSourceNode = ImmediateTextNode() + self.codeSourceNode.alpha = 0.0 + self.codeSourceNode.displaysAsynchronously = false + self.codeSourceNode.attributedText = NSAttributedString(string: code, font: font, textColor: theme.list.itemPrimaryTextColor) + + self.phoneSourceNode = ImmediateTextNode() + self.phoneSourceNode.alpha = 0.0 + self.phoneSourceNode.displaysAsynchronously = false + + let sourceString = NSMutableAttributedString(string: number, font: font, textColor: theme.list.itemPrimaryTextColor) + sourceString.addAttribute(NSAttributedString.Key.kern, value: 1.6, range: NSRange(location: 0, length: sourceString.length)) + self.phoneSourceNode.attributedText = sourceString + + self.codeTargetNode = ImmediateTextNode() + self.codeTargetNode.displaysAsynchronously = false + self.codeTargetNode.attributedText = NSAttributedString(string: code, font: largeFont, textColor: theme.list.itemPrimaryTextColor) + + + self.phoneTargetNode = ImmediateTextNode() + self.phoneTargetNode.displaysAsynchronously = false + + let targetString = NSMutableAttributedString(string: number, font: largeFont, textColor: theme.list.itemPrimaryTextColor) + targetString.addAttribute(NSAttributedString.Key.kern, value: 1.6, range: NSRange(location: 0, length: sourceString.length)) + self.phoneTargetNode.attributedText = targetString + + super.init() + + self.clipsToBounds = false + + self.addSubnode(self.dimNode) + self.addSubnode(self.backgroundNode) + + self.addSubnode(self.codeSourceNode) + self.addSubnode(self.phoneSourceNode) + + self.addSubnode(self.codeTargetNode) + self.addSubnode(self.phoneTargetNode) + + self.addSubnode(self.textNode) + + self.addSubnode(self.cancelButton) + self.addSubnode(self.proceedNode) + + self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), forControlEvents: .touchUpInside) + self.proceedNode.pressed = { [weak self] in + self?.proceed() + } + } + + override func didLoad() { + super.didLoad() + + self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapped))) + } + + @objc private func dimTapped() { + self.cancelPressed() + } + + @objc private func cancelPressed() { + self.dimNode.isUserInteractionEnabled = false + self.cancel() + } + + func animateIn(codeNode: ASDisplayNode, numberNode: ASDisplayNode, buttonNode: ASDisplayNode) { + let codeFrame = codeNode.convert(codeNode.bounds, to: nil) + let numberFrame = numberNode.convert(numberNode.bounds, to: nil) + let buttonFrame = buttonNode.convert(buttonNode.bounds, to: nil) + + codeNode.isHidden = true + numberNode.isHidden = true + buttonNode.isHidden = true + + self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + + let codeSize = self.codeSourceNode.updateLayout(self.frame.size) + self.codeSourceNode.frame = CGRect(origin: CGPoint(x: codeFrame.midX - codeSize.width / 2.0, y: codeFrame.midY - codeSize.height / 2.0), size: codeSize) + + let numberSize = self.phoneSourceNode.updateLayout(self.frame.size) + self.phoneSourceNode.frame = CGRect(origin: CGPoint(x: numberFrame.minX, y: numberFrame.midY - numberSize.height / 2.0), size: numberSize) + + let targetScale = codeSize.height / self.codeTargetNode.frame.height + let sourceScale = self.codeTargetNode.frame.height / codeSize.height + + self.codeSourceNode.layer.animateScale(from: 1.0, to: sourceScale, duration: 0.3) + self.codeSourceNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) + self.codeSourceNode.layer.animatePosition(from: self.codeSourceNode.position, to: self.codeTargetNode.position, duration: 0.3) + + self.phoneSourceNode.layer.animateScale(from: 1.0, to: sourceScale, duration: 0.3) + self.phoneSourceNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) + self.phoneSourceNode.layer.animatePosition(from: self.phoneSourceNode.position, to: self.phoneTargetNode.position, duration: 0.3) + + self.codeTargetNode.layer.animateScale(from: targetScale, to: 1.0, duration: 0.3) + self.codeTargetNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.codeTargetNode.layer.animatePosition(from: self.codeSourceNode.position, to: self.codeTargetNode.position, duration: 0.3) + + self.phoneTargetNode.layer.animateScale(from: targetScale, to: 1.0, duration: 0.3) + self.phoneTargetNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.phoneTargetNode.layer.animatePosition(from: self.phoneSourceNode.position, to: self.phoneTargetNode.position, duration: 0.3) + + self.backgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + self.backgroundNode.layer.animateFrame(from: CGRect(origin: CGPoint(x: 14.0, y: codeFrame.minY), size: CGSize(width: self.backgroundNode.frame.width - 12.0, height: buttonFrame.maxY + 18.0 - codeFrame.minY)), to: self.backgroundNode.frame, duration: 0.3) + + self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.textNode.layer.animateScale(from: 0.5, to: 1.0, duration: 0.3) + self.textNode.layer.animatePosition(from: CGPoint(x: -100.0, y: -45.0), to: CGPoint(), duration: 0.3, additive: true) + + self.cancelButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.cancelButton.layer.animateScale(from: 0.5, to: 1.0, duration: 0.3) + self.cancelButton.layer.animatePosition(from: CGPoint(x: -100.0, y: -70.0), to: CGPoint(), duration: 0.3, additive: true) + + self.proceedNode.layer.animatePosition(from: buttonFrame.center, to: self.proceedNode.position, duration: 0.3) + } + + func animateOut(codeNode: ASDisplayNode, numberNode: ASDisplayNode, buttonNode: ASDisplayNode, completion: @escaping () -> Void) { + let codeFrame = codeNode.convert(codeNode.bounds, to: nil) + let numberFrame = numberNode.convert(numberNode.bounds, to: nil) + let buttonFrame = buttonNode.convert(buttonNode.bounds, to: nil) + + self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + + let codeSize = self.codeSourceNode.updateLayout(self.frame.size) + self.codeSourceNode.frame = CGRect(origin: CGPoint(x: codeFrame.midX - codeSize.width / 2.0, y: codeFrame.midY - codeSize.height / 2.0), size: codeSize) + + let numberSize = self.phoneSourceNode.updateLayout(self.frame.size) + self.phoneSourceNode.frame = CGRect(origin: CGPoint(x: numberFrame.minX, y: numberFrame.midY - numberSize.height / 2.0), size: numberSize) + + let targetScale = codeSize.height / self.codeTargetNode.frame.height + let sourceScale = self.codeTargetNode.frame.height / codeSize.height + + self.codeSourceNode.layer.animateScale(from: sourceScale, to: 1.0, duration: 0.3) + self.codeSourceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.codeSourceNode.layer.animatePosition(from: self.codeTargetNode.position, to: self.codeSourceNode.position, duration: 0.3) + + self.phoneSourceNode.layer.animateScale(from: sourceScale, to: 1.0, duration: 0.3) + self.phoneSourceNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.phoneSourceNode.layer.animatePosition(from: self.phoneTargetNode.position, to: self.phoneSourceNode.position, duration: 0.3) + + self.codeTargetNode.layer.animateScale(from: 1.0, to: targetScale, duration: 0.3) + self.codeTargetNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + self.codeTargetNode.layer.animatePosition(from: self.codeTargetNode.position, to: self.codeSourceNode.position, duration: 0.3) + + Queue.mainQueue().after(0.25) { + codeNode.isHidden = false + numberNode.isHidden = false + buttonNode.isHidden = false + } + + self.phoneTargetNode.layer.animateScale(from: 1.0, to: targetScale, duration: 0.3) + self.phoneTargetNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in + completion() + }) + self.phoneTargetNode.layer.animatePosition(from: self.phoneTargetNode.position, to: self.phoneSourceNode.position, duration: 0.3) + + self.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, delay: 0.1, removeOnCompletion: false) + self.backgroundNode.layer.animateFrame(from: self.backgroundNode.frame, to: CGRect(origin: CGPoint(x: 14.0, y: codeFrame.minY), size: CGSize(width: self.backgroundNode.frame.width - 12.0, height: buttonFrame.maxY + 18.0 - codeFrame.minY)), duration: 0.3) + + self.textNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.textNode.layer.animateScale(from: 1.0, to: 0.5, duration: 0.3, removeOnCompletion: false) + self.textNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -100.0, y: -45.0), duration: 0.3, removeOnCompletion: false, additive: true) + + self.cancelButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.cancelButton.layer.animateScale(from: 1.0, to: 0.5, duration: 0.3, removeOnCompletion: false) + self.cancelButton.layer.animatePosition(from: CGPoint(), to: CGPoint(x: -100.0, y: -70.0), duration: 0.3, removeOnCompletion: false, additive: true) + + self.proceedNode.layer.animatePosition(from: self.proceedNode.position, to: buttonFrame.center, duration: 0.3, removeOnCompletion: false) + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + let sideInset: CGFloat = 8.0 + let innerInset: CGFloat = 18.0 + + transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(x: -layout.size.width, y: 0.0), size: CGSize(width: layout.size.width * 3.0, height: layout.size.height))) + + let backgroundSize = CGSize(width: layout.size.width - sideInset * 2.0, height: 243.0) + let backgroundFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - backgroundSize.width) / 2.0), y: layout.size.height - backgroundSize.height - 260.0), size: backgroundSize) + transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + + let codeSize = self.codeTargetNode.updateLayout(backgroundSize) + let numberSize = self.phoneTargetNode.updateLayout(backgroundSize) + + let totalWidth = codeSize.width + numberSize.width + 10.0 + + let codeFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - totalWidth) / 2.0), y: 30.0), size: codeSize) + transition.updateFrame(node: self.codeTargetNode, frame: codeFrame.offsetBy(dx: backgroundFrame.minX, dy: backgroundFrame.minY)) + + let numberFrame = CGRect(origin: CGPoint(x: codeFrame.maxX + 10.0, y: 30.0), size: numberSize) + transition.updateFrame(node: self.phoneTargetNode, frame: numberFrame.offsetBy(dx: backgroundFrame.minX, dy: backgroundFrame.minY)) + + let textSize = self.textNode.updateLayout(backgroundSize) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - textSize.width) / 2.0), y: 88.0), size: textSize).offsetBy(dx: backgroundFrame.minX, dy: backgroundFrame.minY)) + + let proceedWidth = backgroundSize.width - 16.0 * 2.0 + let proceedHeight = self.proceedNode.updateLayout(width: proceedWidth, transition: transition) + transition.updateFrame(node: self.proceedNode, frame: CGRect(origin: CGPoint(x: innerInset, y: backgroundSize.height - proceedHeight - innerInset), size: CGSize(width: proceedWidth, height: proceedHeight)).offsetBy(dx: backgroundFrame.minX, dy: backgroundFrame.minY)) + + let cancelSize = self.cancelButton.measure(layout.size) + transition.updateFrame(node: self.cancelButton, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - cancelSize.width) / 2.0), y: backgroundSize.height - proceedHeight - innerInset - cancelSize.height - 25.0), size: cancelSize).offsetBy(dx: backgroundFrame.minX, dy: backgroundFrame.minY)) + } + } + + public init(theme: PresentationTheme, strings: PresentationStrings, code: String, number: String, sourceController: AuthorizationSequencePhoneEntryController) { + self.theme = theme + self.strings = strings + self.code = code + self.number = number + self.sourceController = sourceController + + super.init(navigationBarPresentationData: nil) + + self.blocksBackgroundWhenInOverlay = true + + self.statusBar.statusBarStyle = .Ignore + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private var isDismissed = false + override public func loadDisplayNode() { + self.displayNode = Node(theme: self.theme, strings: self.strings, code: self.code, number: self.number) + self.displayNodeDidLoad() + + self.controllerNode.proceed = { [weak self] in + self?.proceed() + } + self.controllerNode.cancel = { [weak self] in + if let strongSelf = self, let sourceController = strongSelf.sourceController { + strongSelf.controllerNode.animateOut(codeNode: sourceController.codeNode, numberNode: sourceController.numberNode, buttonNode: sourceController.buttonNode, completion: { [weak self] in + self?.dismiss() + }) + } + } + } + + func transitionOut() { + self.controllerNode.cancel() + + let transition = ContainedViewLayoutTransition.animated(duration: 0.5, curve: .spring) + transition.updatePosition(layer: self.view.layer, position: CGPoint(x: self.view.center.x - self.view.frame.width, y: self.view.center.y)) + } + + private var didPlayAppearanceAnimation = false + override public func viewDidAppear(_ animated: Bool) { + if !self.didPlayAppearanceAnimation { + self.didPlayAppearanceAnimation = true + if let sourceController = self.sourceController { + self.controllerNode.animateIn(codeNode: sourceController.codeNode, numberNode: sourceController.numberNode, buttonNode: sourceController.buttonNode) + } + } + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.containerLayoutUpdated(layout, transition: transition) + } +} + + +private enum ManagedPhoneAnimationState: CaseIterable, Equatable { + case keypad1 + case keypad2 + case keypad3 + case keypad4 +} + +private final class ManagedPhoneAnimationNode: ManagedAnimationNode { + private var phoneState: ManagedPhoneAnimationState = .keypad1 + private var timer: SwiftSignalKit.Timer? + + init() { + super.init(size: CGSize(width: 100.0, height: 100.0)) + + self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.3)) + } + + func enqueue(_ state: ManagedPhoneAnimationState) { + switch state { + case .keypad1: + self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 10), duration: 0.15)) + self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 10, endFrame: 0), duration: 0.15)) + case .keypad2: + self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 24, endFrame: 34), duration: 0.15)) + self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 34, endFrame: 24), duration: 0.15)) + self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.1)) + case .keypad3: + self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 36, endFrame: 46), duration: 0.15)) + self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 46, endFrame: 36), duration: 0.15)) + self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 0), duration: 0.1)) + case .keypad4: + self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 0, endFrame: 10), duration: 0.15)) + self.trackTo(item: ManagedAnimationItem(source: .local("IntroPhone"), frames: .range(startFrame: 10, endFrame: 0), duration: 0.15)) + } + } +} diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceSignUpController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift similarity index 100% rename from submodules/TelegramUI/Sources/AuthorizationSequenceSignUpController.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpController.swift diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceSignUpControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift similarity index 99% rename from submodules/TelegramUI/Sources/AuthorizationSequenceSignUpControllerNode.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift index 2baa844ae2..3b08f55124 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceSignUpControllerNode.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSignUpControllerNode.swift @@ -5,8 +5,8 @@ import Display import TelegramPresentationData import TextFormat import Markdown -import AuthorizationUI import SolidRoundedButtonNode +import AuthorizationUtils private func roundCorners(diameter: CGFloat) -> UIImage { UIGraphicsBeginImageContextWithOptions(CGSize(width: diameter, height: diameter), false, 0.0) diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceSplashController.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift similarity index 99% rename from submodules/TelegramUI/Sources/AuthorizationSequenceSplashController.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift index b19607034f..823e0ee6b8 100644 --- a/submodules/TelegramUI/Sources/AuthorizationSequenceSplashController.swift +++ b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashController.swift @@ -116,6 +116,10 @@ final class AuthorizationSequenceSplashController: ViewController { return self.startButton.frame } + var buttonTitle: String { + return self.startButton.title ?? "" + } + var animationSnapshot: UIView? { return self.controller.createAnimationSnapshot() } diff --git a/submodules/TelegramUI/Sources/AuthorizationSequenceSplashControllerNode.swift b/submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashControllerNode.swift similarity index 100% rename from submodules/TelegramUI/Sources/AuthorizationSequenceSplashControllerNode.swift rename to submodules/AuthorizationUI/Sources/AuthorizationSequenceSplashControllerNode.swift diff --git a/submodules/AuthorizationUtils/BUILD b/submodules/AuthorizationUtils/BUILD new file mode 100644 index 0000000000..f1c084d619 --- /dev/null +++ b/submodules/AuthorizationUtils/BUILD @@ -0,0 +1,23 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "AuthorizationUtils", + module_name = "AuthorizationUtils", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit:AsyncDisplayKit", + "//submodules/TelegramCore:TelegramCore", + "//submodules/Display:Display", + "//submodules/TextFormat:TextFormat", + "//submodules/Markdown:Markdown", + "//submodules/TelegramPresentationData:TelegramPresentationData", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/AuthorizationUI/Sources/AuthorizationLayout.swift b/submodules/AuthorizationUtils/Sources/AuthorizationLayout.swift similarity index 100% rename from submodules/AuthorizationUI/Sources/AuthorizationLayout.swift rename to submodules/AuthorizationUtils/Sources/AuthorizationLayout.swift diff --git a/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift b/submodules/AuthorizationUtils/Sources/AuthorizationOptionText.swift similarity index 92% rename from submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift rename to submodules/AuthorizationUtils/Sources/AuthorizationOptionText.swift index 8b7401cace..2545b68c86 100644 --- a/submodules/AuthorizationUI/Sources/AuthorizationOptionText.swift +++ b/submodules/AuthorizationUtils/Sources/AuthorizationOptionText.swift @@ -29,10 +29,17 @@ public func authorizationCurrentOptionText(_ type: SentAuthorizationCodeType, ph return NSAttributedString(string: "", font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center) case let .email(emailPattern, _, _, _, _): let mutableString = NSAttributedString(string: strings.Login_EnterCodeEmailText(email ?? emailPattern).string, font: Font.regular(fontSize), textColor: primaryColor, paragraphAlignment: .center).mutableCopy() as! NSMutableAttributedString - let range = (mutableString.string as NSString).range(of: "*******") - if range.location != NSNotFound { - mutableString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true, range: range) + + let string = mutableString.string + let nsString = string as NSString + + if let regex = try? NSRegularExpression(pattern: "\\*", options: []) { + let matches = regex.matches(in: string, options: [], range: NSMakeRange(0, nsString.length)) + if let first = matches.first { + mutableString.addAttribute(NSAttributedString.Key(rawValue: TelegramTextAttributes.Spoiler), value: true, range: NSRange(location: first.range.location, length: matches.count)) + } } + return mutableString } } diff --git a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift index 51de4609a3..d58dd1da43 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchListPaneNode.swift @@ -1696,7 +1696,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in - }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, gallerySource: .custom(messages: foundMessages |> map { message, a, b in + }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, gallerySource: .custom(messages: foundMessages |> map { message, a, b in return (message.map { $0._asMessage() }, a, b) }, messageId: message.id, loadMore: { loadMore() @@ -1815,7 +1815,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode { }, openPeer: { peer, navigation in }, callPeer: { _, _ in }, enqueueMessage: { _ in - }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation, gallerySource: gallerySource)) + }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation, gallerySource: gallerySource)) }, openMessageContextMenu: { [weak self] message, _, node, rect, gesture in guard let strongSelf = self, let currentEntries = strongSelf.currentEntries else { return diff --git a/submodules/Display/Source/Navigation/NavigationController.swift b/submodules/Display/Source/Navigation/NavigationController.swift index c7f517b149..734433942b 100644 --- a/submodules/Display/Source/Navigation/NavigationController.swift +++ b/submodules/Display/Source/Navigation/NavigationController.swift @@ -215,7 +215,7 @@ open class NavigationController: UINavigationController, ContainableController, return self._displayNode! } - var statusBarHost: StatusBarHost? { + public var statusBarHost: StatusBarHost? { didSet { } } diff --git a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift index 1f4e6b248e..234029b427 100644 --- a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift +++ b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift @@ -70,6 +70,11 @@ public final class InAppPurchaseManager: NSObject { return self.numberFormatter.string(from: price) ?? "" } + public func defaultPrice(_ value: NSDecimalNumber, monthsCount: Int) -> String { + let price = value.multiplying(by: NSDecimalNumber(value: monthsCount)).round(2) + return self.numberFormatter.string(from: price) ?? "" + } + public var priceValue: NSDecimalNumber { return self.skProduct.price } diff --git a/submodules/Markdown/Source/Markdown.swift b/submodules/Markdown/Source/Markdown.swift index cd15d81899..3370d0f2f9 100644 --- a/submodules/Markdown/Source/Markdown.swift +++ b/submodules/Markdown/Source/Markdown.swift @@ -124,7 +124,7 @@ public func parseMarkdownIntoAttributedString(_ string: String, attributes: Mark if let bold = parseBold(string: nsString, remainingRange: &remainingRange) { var boldAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: attributes.bold.font, NSAttributedString.Key.foregroundColor: attributes.bold.textColor, NSAttributedString.Key.paragraphStyle: paragraphStyleWithAlignment(textAlignment)] - if !attributes.body.additionalAttributes.isEmpty { + if !attributes.bold.additionalAttributes.isEmpty { for (key, value) in attributes.bold.additionalAttributes { boldAttributes[NSAttributedString.Key(rawValue: key)] = value } diff --git a/submodules/PhoneInputNode/Sources/PhoneInputNode.swift b/submodules/PhoneInputNode/Sources/PhoneInputNode.swift index c82a885224..fdede4f57f 100644 --- a/submodules/PhoneInputNode/Sources/PhoneInputNode.swift +++ b/submodules/PhoneInputNode/Sources/PhoneInputNode.swift @@ -132,6 +132,10 @@ public final class PhoneInputNode: ASDisplayNode, UITextFieldDelegate { private var countryNameForCode: (Int32, String)? + public var formattedCodeAndNumber: (String, String) { + return (self.countryCodeField.textField.text ?? "", self.numberField.textField.text ?? "") + } + public var codeAndNumber: (Int32?, String?, String) { get { var code: Int32? diff --git a/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift b/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift index 0cdf1c5b93..a59f7ece3b 100644 --- a/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift +++ b/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift @@ -58,41 +58,25 @@ class EmojiHeaderComponent: Component { return false } - private var _ready = Promise() + private var _ready = Promise(true) var ready: Signal { return self._ready.get() } weak var animateFrom: UIView? weak var containerView: UIView? - var animationColor: UIColor? - private let sceneView: SCNView let statusView: ComponentHostView - private var previousInteractionTimestamp: Double = 0.0 - private var timer: SwiftSignalKit.Timer? private var hasIdleAnimations = false override init(frame: CGRect) { - self.sceneView = SCNView(frame: CGRect(origin: .zero, size: CGSize(width: 64.0, height: 64.0))) - self.sceneView.backgroundColor = .clear - self.sceneView.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) - self.sceneView.isUserInteractionEnabled = false - self.sceneView.preferredFramesPerSecond = 60 - self.statusView = ComponentHostView() super.init(frame: frame) - self.addSubview(self.sceneView) self.addSubview(self.statusView) - - self.setup() - let tapGestureRecoginzer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:))) - self.addGestureRecognizer(tapGestureRecoginzer) - self.disablesInteractiveModalDismiss = true self.disablesInteractiveTransitionGestureRecognizer = true } @@ -100,42 +84,8 @@ class EmojiHeaderComponent: Component { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - deinit { - self.timer?.invalidate() - } - - private let hapticFeedback = HapticFeedback() - - private var delayTapsTill: Double? - @objc private func handleTap(_ gesture: UITapGestureRecognizer) { - self.playAppearanceAnimation(velocity: nil, mirror: false, explode: true) - } - - private func setup() { - guard let url = getAppBundle().url(forResource: "gift", withExtension: "scn"), let scene = try? SCNScene(url: url, options: nil) else { - return - } - - self.sceneView.scene = scene - self.sceneView.delegate = self - - let _ = self.sceneView.snapshot() - } - - private var didSetReady = false - func renderer(_ renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: TimeInterval) { - if !self.didSetReady { - self.didSetReady = true - - Queue.mainQueue().justDispatch { - self._ready.set(.single(true)) - self.onReady() - } - } - } - - private func maybeAnimateIn() { + + func animateIn() { guard let animateFrom = self.animateFrom, var containerView = self.containerView else { return } @@ -150,8 +100,8 @@ class EmojiHeaderComponent: Component { self.statusView.center = targetPosition animateFrom.alpha = 0.0 - self.statusView.layer.animateScale(from: 0.05, to: 1.0, duration: 0.8, timingFunction: kCAMediaTimingFunctionSpring) - self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.8, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in + self.statusView.layer.animateScale(from: 0.05, to: 1.0, duration: 0.55, timingFunction: kCAMediaTimingFunctionSpring) + self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.55, timingFunction: kCAMediaTimingFunctionSpring, completion: { _ in self.addSubview(self.statusView) self.statusView.center = initialPosition }) @@ -164,129 +114,7 @@ class EmojiHeaderComponent: Component { self.containerView = nil } - private func onReady() { - self.setupScaleAnimation() - - self.maybeAnimateIn() - self.playAppearanceAnimation(explode: true) - - self.previousInteractionTimestamp = CACurrentMediaTime() - self.timer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in - if let strongSelf = self, strongSelf.hasIdleAnimations { - let currentTimestamp = CACurrentMediaTime() - if currentTimestamp > strongSelf.previousInteractionTimestamp + 5.0 { - strongSelf.playAppearanceAnimation() - } - } - }, queue: Queue.mainQueue()) - self.timer?.start() - } - - private func setupScaleAnimation() { -// let animation = CABasicAnimation(keyPath: "transform.scale") -// animation.duration = 2.0 -// animation.fromValue = 1.0 -// animation.toValue = 1.15 -// animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) -// animation.autoreverses = true -// animation.repeatCount = .infinity -// -// self.avatarNode.view.layer.add(animation, forKey: "scale") - } - - private func playAppearanceAnimation(velocity: CGFloat? = nil, smallAngle: Bool = false, mirror: Bool = false, explode: Bool = false) { - guard let scene = self.sceneView.scene else { - return - } - - let currentTime = CACurrentMediaTime() - self.previousInteractionTimestamp = currentTime - self.delayTapsTill = currentTime + 0.85 - - if explode, let node = scene.rootNode.childNode(withName: "swirl", recursively: false), let particlesLeft = scene.rootNode.childNode(withName: "particles_left", recursively: false), let particlesRight = scene.rootNode.childNode(withName: "particles_right", recursively: false), let particlesBottomLeft = scene.rootNode.childNode(withName: "particles_left_bottom", recursively: false), let particlesBottomRight = scene.rootNode.childNode(withName: "particles_right_bottom", recursively: false) { - if let leftParticleSystem = particlesLeft.particleSystems?.first, let rightParticleSystem = particlesRight.particleSystems?.first, let leftBottomParticleSystem = particlesBottomLeft.particleSystems?.first, let rightBottomParticleSystem = particlesBottomRight.particleSystems?.first { - leftParticleSystem.speedFactor = 2.0 - leftParticleSystem.particleVelocity = 1.6 - leftParticleSystem.birthRate = 60.0 - leftParticleSystem.particleLifeSpan = 4.0 - - rightParticleSystem.speedFactor = 2.0 - rightParticleSystem.particleVelocity = 1.6 - rightParticleSystem.birthRate = 60.0 - rightParticleSystem.particleLifeSpan = 4.0 - -// leftBottomParticleSystem.speedFactor = 2.0 - leftBottomParticleSystem.particleVelocity = 1.6 - leftBottomParticleSystem.birthRate = 24.0 - leftBottomParticleSystem.particleLifeSpan = 7.0 - -// rightBottomParticleSystem.speedFactor = 2.0 - rightBottomParticleSystem.particleVelocity = 1.6 - rightBottomParticleSystem.birthRate = 24.0 - rightBottomParticleSystem.particleLifeSpan = 7.0 - - node.physicsField?.isActive = true - Queue.mainQueue().after(1.0) { - node.physicsField?.isActive = false - - leftParticleSystem.birthRate = 12.0 - leftParticleSystem.particleVelocity = 1.2 - leftParticleSystem.particleLifeSpan = 3.0 - - rightParticleSystem.birthRate = 12.0 - rightParticleSystem.particleVelocity = 1.2 - rightParticleSystem.particleLifeSpan = 3.0 - - leftBottomParticleSystem.particleVelocity = 1.2 - leftBottomParticleSystem.birthRate = 7.0 - leftBottomParticleSystem.particleLifeSpan = 5.0 - - rightBottomParticleSystem.particleVelocity = 1.2 - rightBottomParticleSystem.birthRate = 7.0 - rightBottomParticleSystem.particleLifeSpan = 5.0 - - let leftAnimation = POPBasicAnimation() - leftAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in - property?.readBlock = { particleSystem, values in - values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor - } - property?.writeBlock = { particleSystem, values in - (particleSystem as! SCNParticleSystem).speedFactor = values!.pointee - } - property?.threshold = 0.01 - }) as! POPAnimatableProperty) - leftAnimation.fromValue = 1.2 as NSNumber - leftAnimation.toValue = 0.85 as NSNumber - leftAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) - leftAnimation.duration = 0.5 - leftParticleSystem.pop_add(leftAnimation, forKey: "speedFactor") - - let rightAnimation = POPBasicAnimation() - rightAnimation.property = (POPAnimatableProperty.property(withName: "speedFactor", initializer: { property in - property?.readBlock = { particleSystem, values in - values?.pointee = (particleSystem as! SCNParticleSystem).speedFactor - } - property?.writeBlock = { particleSystem, values in - (particleSystem as! SCNParticleSystem).speedFactor = values!.pointee - } - property?.threshold = 0.01 - }) as! POPAnimatableProperty) - rightAnimation.fromValue = 1.2 as NSNumber - rightAnimation.toValue = 0.85 as NSNumber - rightAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) - rightAnimation.duration = 0.5 - rightParticleSystem.pop_add(rightAnimation, forKey: "speedFactor") - } - } - } - } - func update(component: EmojiHeaderComponent, availableSize: CGSize, transition: Transition) -> CGSize { - self.sceneView.bounds = CGRect(origin: .zero, size: CGSize(width: availableSize.width * 2.0, height: availableSize.height * 2.0)) - if self.sceneView.superview == self { - self.sceneView.center = CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0) - } - self.hasIdleAnimations = component.hasIdleAnimations let size = self.statusView.update( @@ -307,7 +135,7 @@ class EmojiHeaderComponent: Component { containerSize: CGSize(width: 96.0, height: 96.0) ) self.statusView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((availableSize.width - size.width) / 2.0), y: 63.0), size: size) - + return availableSize } } diff --git a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift index d28c03bdad..2554a09a50 100644 --- a/submodules/PremiumUI/Sources/PremiumGiftScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumGiftScreen.swift @@ -257,13 +257,16 @@ private final class PremiumGiftScreenContentComponent: CombinedComponent { discount = "" } + let pricePerMonth = product.storeProduct.pricePerMonth(Int(product.months)) + items.append(SectionGroupComponent.Item( AnyComponentWithIdentity( id: product.id, component: AnyComponent( PremiumOptionComponent( title: giftTitle, - totalPrice: product.price, + subtitle: product.price, + labelPrice: pricePerMonth, discount: discount, selected: product.id == component.selectedProductId, primaryTextColor: textColor, diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index dfd58e170d..e5658874e2 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -307,7 +307,8 @@ struct PremiumIntroConfiguration { final class PremiumOptionComponent: CombinedComponent { let title: String - let totalPrice: String + let subtitle: String + let labelPrice: String let discount: String let selected: Bool let primaryTextColor: UIColor @@ -318,7 +319,8 @@ final class PremiumOptionComponent: CombinedComponent { init( title: String, - totalPrice: String, + subtitle: String, + labelPrice: String, discount: String, selected: Bool, primaryTextColor: UIColor, @@ -328,7 +330,8 @@ final class PremiumOptionComponent: CombinedComponent { checkBorderColor: UIColor ) { self.title = title - self.totalPrice = totalPrice + self.subtitle = subtitle + self.labelPrice = labelPrice self.discount = discount self.selected = selected self.primaryTextColor = primaryTextColor @@ -342,7 +345,10 @@ final class PremiumOptionComponent: CombinedComponent { if lhs.title != rhs.title { return false } - if lhs.totalPrice != rhs.totalPrice { + if lhs.subtitle != rhs.subtitle { + return false + } + if lhs.labelPrice != rhs.labelPrice { return false } if lhs.discount != rhs.discount { @@ -372,6 +378,7 @@ final class PremiumOptionComponent: CombinedComponent { static var body: Body { let check = Child(CheckComponent.self) let title = Child(MultilineTextComponent.self) + let subtitle = Child(MultilineTextComponent.self) let discountBackground = Child(RoundedRectangle.self) let discount = Child(MultilineTextComponent.self) let label = Child(MultilineTextComponent.self) @@ -379,13 +386,13 @@ final class PremiumOptionComponent: CombinedComponent { return { context in let component = context.component - let insets = UIEdgeInsets(top: 11.0, left: 46.0, bottom: 13.0, right: 16.0) + var insets = UIEdgeInsets(top: 11.0, left: 46.0, bottom: 13.0, right: 16.0) let label = label.update( component: MultilineTextComponent( text: .plain( NSAttributedString( - string: component.totalPrice, + string: component.labelPrice, font: Font.regular(17), textColor: component.secondaryTextColor ) @@ -410,6 +417,41 @@ final class PremiumOptionComponent: CombinedComponent { availableSize: CGSize(width: context.availableSize.width - insets.left - insets.right - label.size.width, height: context.availableSize.height), transition: context.transition ) + + var spacing: CGFloat = 0.0 + var subtitleHeight: CGFloat = 0.0 + if !component.subtitle.isEmpty { + spacing = 2.0 + + let subtitleFont = Font.regular(13) + let subtitleColor = component.secondaryTextColor + + let subtitleString = parseMarkdownIntoAttributedString( + component.subtitle, + attributes: MarkdownAttributes( + body: MarkdownAttributeSet(font: subtitleFont, textColor: subtitleColor), + bold: MarkdownAttributeSet(font: subtitleFont, textColor: subtitleColor, additionalAttributes: [NSAttributedString.Key.strikethroughStyle.rawValue: NSUnderlineStyle.single.rawValue as NSNumber]), + link: MarkdownAttributeSet(font: subtitleFont, textColor: subtitleColor), + linkAttribute: { _ in return nil } + ) + ) + + let subtitle = subtitle.update( + component: MultilineTextComponent( + text: .plain(subtitleString), + maximumNumberOfLines: 1 + ), + availableSize: CGSize(width: context.availableSize.width - insets.left - insets.right - label.size.width, height: context.availableSize.height), + transition: context.transition + ) + context.add(subtitle + .position(CGPoint(x: insets.left + subtitle.size.width / 2.0, y: insets.top + title.size.height + spacing + subtitle.size.height / 2.0)) + ) + subtitleHeight = subtitle.size.height + + insets.top -= 2.0 + insets.bottom -= 2.0 + } let discountSize: CGSize if !component.discount.isEmpty { @@ -470,7 +512,7 @@ final class PremiumOptionComponent: CombinedComponent { .position(CGPoint(x: insets.left + title.size.width / 2.0, y: insets.top + title.size.height / 2.0)) ) - let size = CGSize(width: context.availableSize.width, height: insets.top + title.size.height + insets.bottom) + let size = CGSize(width: context.availableSize.width, height: insets.top + title.size.height + spacing + subtitleHeight + insets.bottom) context.add(label .position(CGPoint(x: context.availableSize.width - insets.right - label.size.width / 2.0, y: size.height / 2.0)) @@ -1198,72 +1240,210 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { let buy = context.component.buy let updateIsFocused = context.component.updateIsFocused - if state.isPremium == true { - - } else if let products = state.products { - var optionsItems: [SectionGroupComponent.Item] = [] - let gradientColors: [UIColor] = [ - UIColor(rgb: 0x8e77ff), - UIColor(rgb: 0x9a6fff), - UIColor(rgb: 0xb36eee) - ] - - let shortestOptionPrice: Int64 - if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) { - shortestOptionPrice = Int64(Float(product.priceCurrencyAndAmount.amount)) - } else { - shortestOptionPrice = 1 - } - - var i = 0 - for product in products { - let giftTitle: String - let months: Float - if product.id.hasSuffix(".monthly") { - giftTitle = strings.Premium_Monthly - months = 1 + let layoutOptions = { + if state.isPremium == true { + + } else if let products = state.products { + var optionsItems: [SectionGroupComponent.Item] = [] + let gradientColors: [UIColor] = [ + UIColor(rgb: 0x8e77ff), + UIColor(rgb: 0x9a6fff), + UIColor(rgb: 0xb36eee) + ] + + let shortestOptionPrice: (Int64, NSDecimalNumber) + if let product = products.first(where: { $0.id.hasSuffix(".monthly") }) { + shortestOptionPrice = (Int64(Float(product.priceCurrencyAndAmount.amount)), product.priceValue) } else { - giftTitle = strings.Premium_Annual - months = 12 - } - - let discountValue = Int((1.0 - Float(product.priceCurrencyAndAmount.amount) / months / Float(shortestOptionPrice)) * 100.0) - let discount: String - if discountValue > 0 { - discount = "-\(discountValue)%" - } else { - discount = "" + shortestOptionPrice = (1, NSDecimalNumber(decimal: 1)) } - optionsItems.append( - SectionGroupComponent.Item( - AnyComponentWithIdentity( - id: product.id, - component: AnyComponent( - PremiumOptionComponent( - title: giftTitle, - totalPrice: product.price, - discount: discount, - selected: product.id == state.selectedProductId, - primaryTextColor: textColor, - secondaryTextColor: subtitleColor, - accentColor: gradientColors[i], - checkForegroundColor: environment.theme.list.itemCheckColors.foregroundColor, - checkBorderColor: environment.theme.list.itemCheckColors.strokeColor - ) - ) - ), - action: { - selectProduct(product.id) + var i = 0 + for product in products { + let giftTitle: String + let months: Float + if product.id.hasSuffix(".monthly") { + giftTitle = strings.Premium_Monthly + months = 1 + } else { + giftTitle = strings.Premium_Annual + months = 12 + } + + let discountValue = Int((1.0 - Float(product.priceCurrencyAndAmount.amount) / months / Float(shortestOptionPrice.0)) * 100.0) + let discount: String + if discountValue > 0 { + discount = "-\(discountValue)%" + } else { + discount = "" + } + + let defaultPrice = product.defaultPrice(shortestOptionPrice.1, monthsCount: Int(months)) + + var subtitle = "" + var pricePerMonth = product.price + if months > 1 { + pricePerMonth = product.pricePerMonth(Int(months)) + + if defaultPrice != product.price { + subtitle = "**\(defaultPrice)** \(product.price)" + if months == 12 { + subtitle = environment.strings.Premium_PricePerYear(subtitle).string + } + } else { + subtitle = product.price } + } + pricePerMonth = environment.strings.Premium_PricePerMonth(pricePerMonth).string + + optionsItems.append( + SectionGroupComponent.Item( + AnyComponentWithIdentity( + id: product.id, + component: AnyComponent( + PremiumOptionComponent( + title: giftTitle, + subtitle: subtitle, + labelPrice: pricePerMonth, + discount: discount, + selected: product.id == state.selectedProductId, + primaryTextColor: textColor, + secondaryTextColor: subtitleColor, + accentColor: gradientColors[i], + checkForegroundColor: environment.theme.list.itemCheckColors.foregroundColor, + checkBorderColor: environment.theme.list.itemCheckColors.strokeColor + ) + ) + ), + action: { + selectProduct(product.id) + } + ) ) + i += 1 + } + + let optionsSection = optionsSection.update( + component: SectionGroupComponent( + items: optionsItems, + backgroundColor: environment.theme.list.itemBlocksBackgroundColor, + selectionColor: environment.theme.list.itemHighlightedBackgroundColor, + separatorColor: environment.theme.list.itemBlocksSeparatorColor + ), + environment: {}, + availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude), + transition: context.transition ) + context.add(optionsSection + .position(CGPoint(x: availableWidth / 2.0, y: size.height + optionsSection.size.height / 2.0)) + .clipsToBounds(true) + .cornerRadius(10.0) + ) + size.height += optionsSection.size.height + + if case .emojiStatus = context.component.source { + size.height -= 18.0 + } else { + size.height += 26.0 + } + } + } + + let layoutPerks = { + var i = 0 + var perksItems: [SectionGroupComponent.Item] = [] + for perk in state.configuration.perks { + let iconBackgroundColors = gradientColors[i] + perksItems.append(SectionGroupComponent.Item( + AnyComponentWithIdentity( + id: perk.identifier, + component: AnyComponent( + PerkComponent( + iconName: perk.iconName, + iconBackgroundColors: [ + iconBackgroundColors + ], + title: perk.title(strings: strings), + titleColor: titleColor, + subtitle: perk.subtitle(strings: strings), + subtitleColor: subtitleColor, + arrowColor: arrowColor + ) + ) + ), + action: { [weak state] in + var demoSubject: PremiumDemoScreen.Subject + switch perk { + case .doubleLimits: + let isPremium = state?.isPremium == true + + var dismissImpl: (() -> Void)? + let controller = PremimLimitsListScreen(context: accountContext, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "–").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium) + controller.action = { [weak state] in + dismissImpl?() + if state?.isPremium == false { + buy() + } + } + controller.disposed = { + updateIsFocused(false) + } + present(controller) + dismissImpl = { [weak controller] in + controller?.dismiss(animated: true, completion: nil) + } + updateIsFocused(true) + return + case .moreUpload: + demoSubject = .moreUpload + case .fasterDownload: + demoSubject = .fasterDownload + case .voiceToText: + demoSubject = .voiceToText + case .noAds: + demoSubject = .noAds + case .uniqueReactions: + demoSubject = .uniqueReactions + case .premiumStickers: + demoSubject = .premiumStickers + case .advancedChatManagement: + demoSubject = .advancedChatManagement + case .profileBadge: + demoSubject = .profileBadge + case .animatedUserpics: + demoSubject = .animatedUserpics + case .appIcons: + demoSubject = .appIcons + case .animatedEmoji: + demoSubject = .animatedEmoji + } + + let controller = PremiumDemoScreen( + context: accountContext, + subject: demoSubject, + source: .intro(state?.price), + order: state?.configuration.perks, + action: { + if state?.isPremium == false { + buy() + } + } + ) + controller.disposed = { + updateIsFocused(false) + } + present(controller) + updateIsFocused(true) + + addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier]) + } + )) i += 1 } - let optionsSection = optionsSection.update( + let perksSection = perksSection.update( component: SectionGroupComponent( - items: optionsItems, + items: perksItems, backgroundColor: environment.theme.list.itemBlocksBackgroundColor, selectionColor: environment.theme.list.itemHighlightedBackgroundColor, separatorColor: environment.theme.list.itemBlocksSeparatorColor @@ -1272,264 +1452,172 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude), transition: context.transition ) - context.add(optionsSection - .position(CGPoint(x: availableWidth / 2.0, y: size.height + optionsSection.size.height / 2.0)) + context.add(perksSection + .position(CGPoint(x: availableWidth / 2.0, y: size.height + perksSection.size.height / 2.0)) .clipsToBounds(true) .cornerRadius(10.0) ) - size.height += optionsSection.size.height - size.height += 26.0 - } - - var i = 0 - var perksItems: [SectionGroupComponent.Item] = [] - for perk in state.configuration.perks { - let iconBackgroundColors = gradientColors[i] - perksItems.append(SectionGroupComponent.Item( - AnyComponentWithIdentity( - id: perk.identifier, - component: AnyComponent( - PerkComponent( - iconName: perk.iconName, - iconBackgroundColors: [ - iconBackgroundColors - ], - title: perk.title(strings: strings), - titleColor: titleColor, - subtitle: perk.subtitle(strings: strings), - subtitleColor: subtitleColor, - arrowColor: arrowColor - ) - ) - ), - action: { [weak state] in - var demoSubject: PremiumDemoScreen.Subject - switch perk { - case .doubleLimits: - let isPremium = state?.isPremium == true - - var dismissImpl: (() -> Void)? - let controller = PremimLimitsListScreen(context: accountContext, buttonText: isPremium ? strings.Common_OK : (state?.isAnnual == true ? strings.Premium_SubscribeForAnnual(state?.price ?? "–").string : strings.Premium_SubscribeFor(state?.price ?? "–").string), isPremium: isPremium) - controller.action = { [weak state] in - dismissImpl?() - if state?.isPremium == false { - buy() - } - } - controller.disposed = { - updateIsFocused(false) - } - present(controller) - dismissImpl = { [weak controller] in - controller?.dismiss(animated: true, completion: nil) - } - updateIsFocused(true) - return - case .moreUpload: - demoSubject = .moreUpload - case .fasterDownload: - demoSubject = .fasterDownload - case .voiceToText: - demoSubject = .voiceToText - case .noAds: - demoSubject = .noAds - case .uniqueReactions: - demoSubject = .uniqueReactions - case .premiumStickers: - demoSubject = .premiumStickers - case .advancedChatManagement: - demoSubject = .advancedChatManagement - case .profileBadge: - demoSubject = .profileBadge - case .animatedUserpics: - demoSubject = .animatedUserpics - case .appIcons: - demoSubject = .appIcons - case .animatedEmoji: - demoSubject = .animatedEmoji - } - - let controller = PremiumDemoScreen( - context: accountContext, - subject: demoSubject, - source: .intro(state?.price), - order: state?.configuration.perks, - action: { - if state?.isPremium == false { - buy() - } - } - ) - controller.disposed = { - updateIsFocused(false) - } - present(controller) - updateIsFocused(true) - - addAppLogEvent(postbox: accountContext.account.postbox, type: "premium.promo_screen_tap", data: ["item": perk.identifier]) + size.height += perksSection.size.height + + if case .emojiStatus = context.component.source { + if state.isPremium == true { + size.height -= 23.0 + } else { + size.height += 23.0 } - )) - i += 1 - } - - let perksSection = perksSection.update( - component: SectionGroupComponent( - items: perksItems, - backgroundColor: environment.theme.list.itemBlocksBackgroundColor, - selectionColor: environment.theme.list.itemHighlightedBackgroundColor, - separatorColor: environment.theme.list.itemBlocksSeparatorColor - ), - environment: {}, - availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude), - transition: context.transition - ) - context.add(perksSection - .position(CGPoint(x: availableWidth / 2.0, y: size.height + perksSection.size.height / 2.0)) - .clipsToBounds(true) - .cornerRadius(10.0) - ) - size.height += perksSection.size.height - size.height += 23.0 - - let textSideInset: CGFloat = 16.0 - let textPadding: CGFloat = 13.0 - - let infoTitle = infoTitle.update( - component: MultilineTextComponent( - text: .plain( - NSAttributedString(string: strings.Premium_AboutTitle.uppercased(), font: Font.regular(14.0), textColor: environment.theme.list.freeTextColor) - ), - horizontalAlignment: .natural, - maximumNumberOfLines: 0, - lineSpacing: 0.2 - ), - environment: {}, - availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude), - transition: context.transition - ) - context.add(infoTitle - .position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + infoTitle.size.width / 2.0, y: size.height + infoTitle.size.height / 2.0)) - ) - size.height += infoTitle.size.height - size.height += 3.0 - - let infoText = infoText.update( - component: MultilineTextComponent( - text: .markdown( - text: strings.Premium_AboutText, - attributes: markdownAttributes - ), - horizontalAlignment: .natural, - maximumNumberOfLines: 0, - lineSpacing: 0.2 - ), - environment: {}, - availableSize: CGSize(width: availableWidth - sideInsets - textSideInset * 2.0, height: .greatestFiniteMagnitude), - transition: context.transition - ) - - let infoBackground = infoBackground.update( - component: RoundedRectangle( - color: environment.theme.list.itemBlocksBackgroundColor, - cornerRadius: 10.0 - ), - environment: {}, - availableSize: CGSize(width: availableWidth - sideInsets, height: infoText.size.height + textPadding * 2.0), - transition: context.transition - ) - context.add(infoBackground - .position(CGPoint(x: size.width / 2.0, y: size.height + infoBackground.size.height / 2.0)) - ) - context.add(infoText - .position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + infoText.size.width / 2.0, y: size.height + textPadding + infoText.size.height / 2.0)) - ) - size.height += infoBackground.size.height - size.height += 6.0 - - let termsFont = Font.regular(13.0) - let boldTermsFont = Font.semibold(13.0) - let italicTermsFont = Font.italic(13.0) - let boldItalicTermsFont = Font.semiboldItalic(13.0) - let monospaceTermsFont = Font.monospace(13.0) - let termsTextColor = environment.theme.list.freeTextColor - let termsMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: termsFont, textColor: termsTextColor), bold: MarkdownAttributeSet(font: termsFont, textColor: termsTextColor), link: MarkdownAttributeSet(font: termsFont, textColor: environment.theme.list.itemAccentColor), linkAttribute: { contents in - return (TelegramTextAttributes.URL, contents) - }) - - var isGiftView = false - if case let .gift(fromId, _, _) = context.component.source { - if fromId == context.component.context.account.peerId { - isGiftView = true + } else { + size.height += 23.0 } } - let termsString: MultilineTextComponent.TextContent - if isGiftView { - termsString = .plain(NSAttributedString()) - } else if let promoConfiguration = context.state.promoConfiguration { - let attributedString = stringWithAppliedEntities(promoConfiguration.status, entities: promoConfiguration.statusEntities, baseColor: termsTextColor, linkColor: environment.theme.list.itemAccentColor, baseFont: termsFont, linkFont: termsFont, boldFont: boldTermsFont, italicFont: italicTermsFont, boldItalicFont: boldItalicTermsFont, fixedFont: monospaceTermsFont, blockQuoteFont: termsFont, message: nil) - termsString = .plain(attributedString) + if case .emojiStatus = context.component.source { + layoutPerks() + layoutOptions() } else { - termsString = .markdown( - text: strings.Premium_Terms, - attributes: termsMarkdownAttributes + layoutOptions() + layoutPerks() + + let textSideInset: CGFloat = 16.0 + let textPadding: CGFloat = 13.0 + + let infoTitle = infoTitle.update( + component: MultilineTextComponent( + text: .plain( + NSAttributedString(string: strings.Premium_AboutTitle.uppercased(), font: Font.regular(14.0), textColor: environment.theme.list.freeTextColor) + ), + horizontalAlignment: .natural, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + environment: {}, + availableSize: CGSize(width: availableWidth - sideInsets, height: .greatestFiniteMagnitude), + transition: context.transition ) - } - - let termsText = termsText.update( - component: MultilineTextComponent( - text: termsString, - horizontalAlignment: .natural, - maximumNumberOfLines: 0, - lineSpacing: 0.0, - highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.3), - highlightAction: { attributes in - if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { - return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) - } else { - return nil - } - }, - tapAction: { [weak environment] attributes, _ in - if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String, - let controller = environment?.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController { - if url.hasPrefix("https://apps.apple.com/account/subscriptions") { - controller.context.sharedContext.applicationBindings.openSubscriptions() - } else if url.hasPrefix("https://") || url.hasPrefix("tg://") { - controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: url, forceExternal: !url.hasPrefix("tg://"), presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: nil, dismissInput: {}) + context.add(infoTitle + .position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + infoTitle.size.width / 2.0, y: size.height + infoTitle.size.height / 2.0)) + ) + size.height += infoTitle.size.height + size.height += 3.0 + + let infoText = infoText.update( + component: MultilineTextComponent( + text: .markdown( + text: strings.Premium_AboutText, + attributes: markdownAttributes + ), + horizontalAlignment: .natural, + maximumNumberOfLines: 0, + lineSpacing: 0.2 + ), + environment: {}, + availableSize: CGSize(width: availableWidth - sideInsets - textSideInset * 2.0, height: .greatestFiniteMagnitude), + transition: context.transition + ) + + let infoBackground = infoBackground.update( + component: RoundedRectangle( + color: environment.theme.list.itemBlocksBackgroundColor, + cornerRadius: 10.0 + ), + environment: {}, + availableSize: CGSize(width: availableWidth - sideInsets, height: infoText.size.height + textPadding * 2.0), + transition: context.transition + ) + context.add(infoBackground + .position(CGPoint(x: size.width / 2.0, y: size.height + infoBackground.size.height / 2.0)) + ) + context.add(infoText + .position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + infoText.size.width / 2.0, y: size.height + textPadding + infoText.size.height / 2.0)) + ) + size.height += infoBackground.size.height + size.height += 6.0 + + let termsFont = Font.regular(13.0) + let boldTermsFont = Font.semibold(13.0) + let italicTermsFont = Font.italic(13.0) + let boldItalicTermsFont = Font.semiboldItalic(13.0) + let monospaceTermsFont = Font.monospace(13.0) + let termsTextColor = environment.theme.list.freeTextColor + let termsMarkdownAttributes = MarkdownAttributes(body: MarkdownAttributeSet(font: termsFont, textColor: termsTextColor), bold: MarkdownAttributeSet(font: termsFont, textColor: termsTextColor), link: MarkdownAttributeSet(font: termsFont, textColor: environment.theme.list.itemAccentColor), linkAttribute: { contents in + return (TelegramTextAttributes.URL, contents) + }) + + var isGiftView = false + if case let .gift(fromId, _, _) = context.component.source { + if fromId == context.component.context.account.peerId { + isGiftView = true + } + } + + let termsString: MultilineTextComponent.TextContent + if isGiftView { + termsString = .plain(NSAttributedString()) + } else if let promoConfiguration = context.state.promoConfiguration { + let attributedString = stringWithAppliedEntities(promoConfiguration.status, entities: promoConfiguration.statusEntities, baseColor: termsTextColor, linkColor: environment.theme.list.itemAccentColor, baseFont: termsFont, linkFont: termsFont, boldFont: boldTermsFont, italicFont: italicTermsFont, boldItalicFont: boldItalicTermsFont, fixedFont: monospaceTermsFont, blockQuoteFont: termsFont, message: nil) + termsString = .plain(attributedString) + } else { + termsString = .markdown( + text: strings.Premium_Terms, + attributes: termsMarkdownAttributes + ) + } + + let termsText = termsText.update( + component: MultilineTextComponent( + text: termsString, + horizontalAlignment: .natural, + maximumNumberOfLines: 0, + lineSpacing: 0.0, + highlightColor: environment.theme.list.itemAccentColor.withAlphaComponent(0.3), + highlightAction: { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] { + return NSAttributedString.Key(rawValue: TelegramTextAttributes.URL) } else { - let context = controller.context - let signal: Signal? - switch url { - case "terms": - signal = cachedTermsPage(context: context) - case "privacy": - signal = cachedPrivacyPage(context: context) - default: - signal = nil - } - if let signal = signal { - let _ = (signal - |> deliverOnMainQueue).start(next: { resolvedUrl in - context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in - }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak controller] c, arguments in - controller?.push(c) - }, dismissInput: {}, contentContext: nil) - }) + return nil + } + }, + tapAction: { [weak environment] attributes, _ in + if let url = attributes[NSAttributedString.Key(rawValue: TelegramTextAttributes.URL)] as? String, + let controller = environment?.controller() as? PremiumIntroScreen, let navigationController = controller.navigationController as? NavigationController { + if url.hasPrefix("https://apps.apple.com/account/subscriptions") { + controller.context.sharedContext.applicationBindings.openSubscriptions() + } else if url.hasPrefix("https://") || url.hasPrefix("tg://") { + controller.context.sharedContext.openExternalUrl(context: controller.context, urlContext: .generic, url: url, forceExternal: !url.hasPrefix("tg://"), presentationData: controller.context.sharedContext.currentPresentationData.with({$0}), navigationController: nil, dismissInput: {}) + } else { + let context = controller.context + let signal: Signal? + switch url { + case "terms": + signal = cachedTermsPage(context: context) + case "privacy": + signal = cachedPrivacyPage(context: context) + default: + signal = nil + } + if let signal = signal { + let _ = (signal + |> deliverOnMainQueue).start(next: { resolvedUrl in + context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigation in + }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { [weak controller] c, arguments in + controller?.push(c) + }, dismissInput: {}, contentContext: nil) + }) + } } } } - } - ), - environment: {}, - availableSize: CGSize(width: availableWidth - sideInsets - textSideInset * 2.0, height: .greatestFiniteMagnitude), - transition: context.transition - ) - context.add(termsText - .position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + termsText.size.width / 2.0, y: size.height + termsText.size.height / 2.0)) - ) - size.height += termsText.size.height - size.height += 10.0 + ), + environment: {}, + availableSize: CGSize(width: availableWidth - sideInsets - textSideInset * 2.0, height: .greatestFiniteMagnitude), + transition: context.transition + ) + context.add(termsText + .position(CGPoint(x: sideInset + environment.safeInsets.left + textSideInset + termsText.size.width / 2.0, y: size.height + termsText.size.height / 2.0)) + ) + size.height += termsText.size.height + size.height += 10.0 + } + size.height += scrollEnvironment.insets.bottom if context.component.source != .settings { @@ -1713,10 +1801,11 @@ private final class PremiumIntroScreenComponent: CombinedComponent { otherPeerName ).start(next: { [weak self] products, isPremium, otherPeerName in if let strongSelf = self { - if strongSelf.products == nil { - strongSelf.selectedProductId = products.first?.id - } + let hadProducts = strongSelf.products != nil strongSelf.products = products.filter { $0.isSubscription } + if !hadProducts { + strongSelf.selectedProductId = strongSelf.products?.last?.id + } strongSelf.isPremium = isPremium strongSelf.otherPeerName = otherPeerName strongSelf.updated(transition: .immediate) @@ -1786,7 +1875,6 @@ private final class PremiumIntroScreenComponent: CombinedComponent { strongSelf.updateInProgress(false) strongSelf.updated(transition: .immediate) - strongSelf.completion() addAppLogEvent(postbox: strongSelf.context.account.postbox, type: "premium.promo_screen_fail") @@ -2366,7 +2454,10 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { if let sourceView = self.sourceView { view.animateFrom = sourceView view.containerView = self.containerView - view.animationColor = self.animationColor + + Queue.mainQueue().after(0.1) { + view.animateIn() + } self.sourceView = nil self.containerView = nil diff --git a/submodules/PresentationDataUtils/Sources/SolidRoundedButtonNode.swift b/submodules/PresentationDataUtils/Sources/SolidRoundedButtonNode.swift index d708784757..0d7d8b9517 100644 --- a/submodules/PresentationDataUtils/Sources/SolidRoundedButtonNode.swift +++ b/submodules/PresentationDataUtils/Sources/SolidRoundedButtonNode.swift @@ -5,6 +5,6 @@ import TelegramPresentationData public extension SolidRoundedButtonTheme { convenience init(theme: PresentationTheme) { - self.init(backgroundColor: theme.list.itemCheckColors.fillColor, backgroundColors: [], foregroundColor: theme.list.itemCheckColors.foregroundColor) + self.init(backgroundColor: theme.list.itemCheckColors.fillColor, backgroundColors: [], foregroundColor: theme.list.itemCheckColors.foregroundColor, disabledBackgroundColor: theme.list.plainBackgroundColor.mixedWith(theme.list.itemDisabledTextColor, alpha: 0.15), disabledForegroundColor: theme.list.itemDisabledTextColor) } } diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD index 6f77d2566e..8cf517f613 100644 --- a/submodules/SettingsUI/BUILD +++ b/submodules/SettingsUI/BUILD @@ -73,6 +73,7 @@ swift_library( "//submodules/HexColor:HexColor", "//submodules/QrCode:QrCode", "//submodules/WallpaperResources:WallpaperResources", + "//submodules/AuthorizationUtils:AuthorizationUtils", "//submodules/AuthorizationUI:AuthorizationUI", "//submodules/InstantPageUI:InstantPageUI", "//submodules/CheckNode:CheckNode", diff --git a/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift b/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift index 09a1b28010..1f1707190a 100644 --- a/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift +++ b/submodules/SettingsUI/Sources/ChangePhoneNumberCodeController.swift @@ -11,7 +11,7 @@ import OverlayStatusController import AccountContext import AlertUI import PresentationDataUtils -import AuthorizationUI +import AuthorizationUtils import PhoneNumberFormat private final class ChangePhoneNumberCodeControllerArguments { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift b/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift index 1cc0b8723b..09e42e9e5c 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/ConfirmPhoneNumberController.swift @@ -10,7 +10,7 @@ import PresentationDataUtils import AccountContext import AlertUI import PresentationDataUtils -import AuthorizationUI +import AuthorizationUtils import PhoneNumberFormat private final class ConfirmPhoneNumberCodeControllerArguments { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index 23dc215f07..6a7e2615c5 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -16,6 +16,7 @@ import AppBundle import PasswordSetupUI import UndoUI import PremiumUI +import AuthorizationUI private final class PrivacyAndSecurityControllerArguments { let account: Account @@ -33,8 +34,9 @@ private final class PrivacyAndSecurityControllerArguments { let toggleArchiveAndMuteNonContacts: (Bool) -> Void let setupAccountAutoremove: () -> Void let openDataSettings: () -> Void + let openEmailSettings: (String?) -> Void - init(account: Account, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openProfilePhotoPrivacy: @escaping () -> Void, openForwardPrivacy: @escaping () -> Void, openPhoneNumberPrivacy: @escaping () -> Void, openVoiceMessagePrivacy: @escaping () -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping (TwoStepVerificationAccessConfiguration?) -> Void, openActiveSessions: @escaping () -> Void, toggleArchiveAndMuteNonContacts: @escaping (Bool) -> Void, setupAccountAutoremove: @escaping () -> Void, openDataSettings: @escaping () -> Void) { + init(account: Account, openBlockedUsers: @escaping () -> Void, openLastSeenPrivacy: @escaping () -> Void, openGroupsPrivacy: @escaping () -> Void, openVoiceCallPrivacy: @escaping () -> Void, openProfilePhotoPrivacy: @escaping () -> Void, openForwardPrivacy: @escaping () -> Void, openPhoneNumberPrivacy: @escaping () -> Void, openVoiceMessagePrivacy: @escaping () -> Void, openPasscode: @escaping () -> Void, openTwoStepVerification: @escaping (TwoStepVerificationAccessConfiguration?) -> Void, openActiveSessions: @escaping () -> Void, toggleArchiveAndMuteNonContacts: @escaping (Bool) -> Void, setupAccountAutoremove: @escaping () -> Void, openDataSettings: @escaping () -> Void, openEmailSettings: @escaping (String?) -> Void) { self.account = account self.openBlockedUsers = openBlockedUsers self.openLastSeenPrivacy = openLastSeenPrivacy @@ -50,6 +52,7 @@ private final class PrivacyAndSecurityControllerArguments { self.toggleArchiveAndMuteNonContacts = toggleArchiveAndMuteNonContacts self.setupAccountAutoremove = setupAccountAutoremove self.openDataSettings = openDataSettings + self.openEmailSettings = openEmailSettings } } @@ -87,6 +90,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { case selectivePrivacyInfo(PresentationTheme, String) case passcode(PresentationTheme, String, Bool, String) case twoStepVerification(PresentationTheme, String, String, TwoStepVerificationAccessConfiguration?) + case loginEmail(PresentationTheme, String, String?) case activeSessions(PresentationTheme, String, String) case autoArchiveHeader(String) case autoArchive(String, Bool) @@ -99,7 +103,7 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { var section: ItemListSectionId { switch self { - case .blockedPeers, .activeSessions, .passcode, .twoStepVerification: + case .blockedPeers, .activeSessions, .passcode, .twoStepVerification, .loginEmail: return PrivacyAndSecuritySection.general.rawValue case .privacyHeader, .phoneNumberPrivacy, .lastSeenPrivacy, .profilePhotoPrivacy, .forwardPrivacy, .groupPrivacy, .voiceCallPrivacy, .voiceMessagePrivacy, .selectivePrivacyInfo: return PrivacyAndSecuritySection.privacy.rawValue @@ -122,40 +126,42 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { return 3 case .twoStepVerification: return 4 - case .privacyHeader: + case .loginEmail: return 5 - case .phoneNumberPrivacy: + case .privacyHeader: return 6 - case .lastSeenPrivacy: + case .phoneNumberPrivacy: return 7 - case .profilePhotoPrivacy: + case .lastSeenPrivacy: return 8 - case .voiceCallPrivacy: + case .profilePhotoPrivacy: return 9 - case .voiceMessagePrivacy: + case .voiceCallPrivacy: return 10 - case .forwardPrivacy: + case .voiceMessagePrivacy: return 11 - case .groupPrivacy: + case .forwardPrivacy: return 12 - case .selectivePrivacyInfo: + case .groupPrivacy: return 13 - case .autoArchiveHeader: + case .selectivePrivacyInfo: return 14 - case .autoArchive: + case .autoArchiveHeader: return 15 - case .autoArchiveInfo: + case .autoArchive: return 16 - case .accountHeader: + case .autoArchiveInfo: return 17 - case .accountTimeout: + case .accountHeader: return 18 - case .accountInfo: + case .accountTimeout: return 19 - case .dataSettings: + case .accountInfo: return 20 - case .dataSettingsInfo: + case .dataSettings: return 21 + case .dataSettingsInfo: + return 22 } } @@ -233,6 +239,12 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { } else { return false } + case let .loginEmail(lhsTheme, lhsText, lhsEmailPattern): + if case let .loginEmail(rhsTheme, rhsText, rhsEmailPattern) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsEmailPattern == rhsEmailPattern { + return true + } else { + return false + } case let .activeSessions(lhsTheme, lhsText, lhsValue): if case let .activeSessions(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true @@ -341,6 +353,10 @@ private enum PrivacyAndSecurityEntry: ItemListNodeEntry { return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/TwoStepAuth")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openTwoStepVerification(data) }) + case let .loginEmail(_, text, emailPattern): + return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/LoginEmail")?.precomposed(), title: text, label: "", sectionId: self.section, style: .blocks, action: { + arguments.openEmailSettings(emailPattern) + }) case let .activeSessions(_, text, value): return ItemListDisclosureItem(presentationData: presentationData, icon: UIImage(bundleImageName: "Settings/Menu/Websites")?.precomposed(), title: text, label: value, sectionId: self.section, style: .blocks, action: { arguments.openActiveSessions() @@ -411,7 +427,20 @@ private func stringForSelectiveSettings(strings: PresentationStrings, settings: } } -private func privacyAndSecurityControllerEntries(presentationData: PresentationData, state: PrivacyAndSecurityControllerState, privacySettings: AccountPrivacySettings?, accessChallengeData: PostboxAccessChallengeData, blockedPeerCount: Int?, activeWebsitesCount: Int, hasTwoStepAuth: Bool?, twoStepAuthData: TwoStepVerificationAccessConfiguration?, canAutoarchive: Bool, isPremiumDisabled: Bool, isPremium: Bool) -> [PrivacyAndSecurityEntry] { +private func privacyAndSecurityControllerEntries( + presentationData: PresentationData, + state: PrivacyAndSecurityControllerState, + privacySettings: AccountPrivacySettings?, + accessChallengeData: PostboxAccessChallengeData, + blockedPeerCount: Int?, + activeWebsitesCount: Int, + hasTwoStepAuth: Bool?, + twoStepAuthData: TwoStepVerificationAccessConfiguration?, + canAutoarchive: Bool, + isPremiumDisabled: Bool, + isPremium: Bool, + loginEmail: String? +) -> [PrivacyAndSecurityEntry] { var entries: [PrivacyAndSecurityEntry] = [] entries.append(.blockedPeers(presentationData.theme, presentationData.strings.Settings_BlockedUsers, blockedPeerCount == nil ? "" : (blockedPeerCount == 0 ? presentationData.strings.PrivacySettings_BlockedPeersEmpty : "\(blockedPeerCount!)"))) @@ -443,6 +472,8 @@ private func privacyAndSecurityControllerEntries(presentationData: PresentationD } entries.append(.twoStepVerification(presentationData.theme, presentationData.strings.PrivacySettings_TwoStepAuth, twoStepAuthString, twoStepAuthData)) + entries.append(.loginEmail(presentationData.theme, presentationData.strings.PrivacySettings_LoginEmail, loginEmail)) + entries.append(.privacyHeader(presentationData.theme, presentationData.strings.PrivacySettings_PrivacyTitle)) if let privacySettings = privacySettings { entries.append(.phoneNumberPrivacy(presentationData.theme, presentationData.strings.PrivacySettings_PhoneNumber, stringForSelectiveSettings(strings: presentationData.strings, settings: privacySettings.phoneNumber))) @@ -509,7 +540,7 @@ class PrivacyAndSecurityControllerImpl: ItemListController { } -public func privacyAndSecurityController(context: AccountContext, initialSettings: AccountPrivacySettings? = nil, updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil, updatedBlockedPeers: ((BlockedPeersContext?) -> Void)? = nil, updatedHasTwoStepAuth: ((Bool) -> Void)? = nil, focusOnItemTag: PrivacyAndSecurityEntryTag? = nil, activeSessionsContext: ActiveSessionsContext? = nil, webSessionsContext: WebSessionsContext? = nil, blockedPeersContext: BlockedPeersContext? = nil, hasTwoStepAuth: Bool? = nil) -> ViewController { +public func privacyAndSecurityController(context: AccountContext, initialSettings: AccountPrivacySettings? = nil, updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil, updatedBlockedPeers: ((BlockedPeersContext?) -> Void)? = nil, updatedHasTwoStepAuth: ((Bool) -> Void)? = nil, focusOnItemTag: PrivacyAndSecurityEntryTag? = nil, activeSessionsContext: ActiveSessionsContext? = nil, webSessionsContext: WebSessionsContext? = nil, blockedPeersContext: BlockedPeersContext? = nil, hasTwoStepAuth: Bool? = nil, loginEmailPattern: Signal? = nil) -> ViewController { let statePromise = ValuePromise(PrivacyAndSecurityControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: PrivacyAndSecurityControllerState()) let updateState: ((PrivacyAndSecurityControllerState) -> PrivacyAndSecurityControllerState) -> Void = { f in @@ -568,6 +599,23 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting twoStepAuth.set(hasTwoStepAuthDataValue) } + let loginEmail: Signal + if let loginEmailPattern = loginEmailPattern { + loginEmail = loginEmailPattern + } else { + loginEmail = .single(nil) + |> then( + context.engine.auth.twoStepAuthData() + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> map { data -> String? in + return data?.loginEmailPattern + } + ) + } + let updateHasTwoStepAuth: () -> Void = { let signal = context.engine.auth.twoStepVerificationConfiguration() |> map { value -> TwoStepVerificationAccessConfiguration? in @@ -589,6 +637,8 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting } updateHasTwoStepAuth() + var setupEmailImpl: ((String?) -> Void)? + let arguments = PrivacyAndSecurityControllerArguments(account: context.account, openBlockedUsers: { pushControllerImpl?(blockedPeersController(context: context, blockedPeersContext: blockedPeersContext), true) }, openLastSeenPrivacy: { @@ -926,6 +976,23 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting })) }, openDataSettings: { pushControllerImpl?(dataPrivacyController(context: context), true) + }, openEmailSettings: { emailPattern in + if let emailPattern = emailPattern { + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + let controller = textAlertController( + context: context, title: emailPattern, text: "This email address will be used every time you login to your Telegram account from a new device.", actions: [ + TextAlertAction(type: .genericAction, title: "Change Email", action: { + setupEmailImpl?(emailPattern) + }), + TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: { + + }) + ], actionLayout: .vertical + ) + presentControllerImpl?(controller) + } else { + setupEmailImpl?(nil) + } }) actionsDisposable.add(context.engine.peers.managedUpdatedRecentPeers().start()) @@ -953,9 +1020,10 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting context.sharedContext.accountManager.accessChallengeData(), combineLatest(twoStepAuth.get(), twoStepAuthDataValue.get()), context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App()), - context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) + context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)), + loginEmail ) - |> map { presentationData, state, privacySettings, noticeView, sharedData, recentPeers, blockedPeersState, activeWebsitesState, accessChallengeData, twoStepAuth, appConfiguration, accountPeer -> (ItemListControllerState, (ItemListNodeState, Any)) in + |> map { presentationData, state, privacySettings, noticeView, sharedData, recentPeers, blockedPeersState, activeWebsitesState, accessChallengeData, twoStepAuth, appConfiguration, accountPeer, loginEmail -> (ItemListControllerState, (ItemListNodeState, Any)) in var canAutoarchive = false if let data = appConfiguration.data, let hasAutoarchive = data["autoarchive_setting_available"] as? Bool { canAutoarchive = hasAutoarchive @@ -971,7 +1039,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting let isPremium = accountPeer?.isPremium ?? false let isPremiumDisabled = PremiumConfiguration.with(appConfiguration: context.currentAppConfiguration.with { $0 }).isPremiumDisabled - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: privacyAndSecurityControllerEntries(presentationData: presentationData, state: state, privacySettings: privacySettings, accessChallengeData: accessChallengeData.data, blockedPeerCount: blockedPeersState.totalCount, activeWebsitesCount: activeWebsitesState.sessions.count, hasTwoStepAuth: twoStepAuth.0, twoStepAuthData: twoStepAuth.1, canAutoarchive: canAutoarchive, isPremiumDisabled: isPremiumDisabled, isPremium: isPremium), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: privacyAndSecurityControllerEntries(presentationData: presentationData, state: state, privacySettings: privacySettings, accessChallengeData: accessChallengeData.data, blockedPeerCount: blockedPeersState.totalCount, activeWebsitesCount: activeWebsitesState.sessions.count, hasTwoStepAuth: twoStepAuth.0, twoStepAuthData: twoStepAuth.1, canAutoarchive: canAutoarchive, isPremiumDisabled: isPremiumDisabled, isPremium: isPremium, loginEmail: loginEmail), style: .blocks, ensureVisibleItemTag: focusOnItemTag, animateChanges: false) return (controllerState, (listState, arguments)) } @@ -997,5 +1065,60 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting updateHasTwoStepAuth() } + setupEmailImpl = { emailPattern in + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + var dismissEmailControllerImpl: (() -> Void)? + let emailController = AuthorizationSequenceEmailEntryController(presentationData: presentationData, mode: emailPattern != nil ? .change : .setup, back: { + dismissEmailControllerImpl?() + }) + emailController.proceedWithEmail = { [weak emailController] email in + emailController?.inProgress = true + + actionsDisposable.add((sendLoginEmailChangeCode(account: context.account, email: email) + |> deliverOnMainQueue).start(next: { data in + var dismissCodeControllerImpl: (() -> Void)? + let codeController = AuthorizationSequenceCodeEntryController(presentationData: presentationData, openUrl: { _ in }, back: { + dismissCodeControllerImpl?() + }) + codeController.loginWithCode = { code in + + } + codeController.signInWithApple = { + + } + codeController.updateData(number: "", email: email, codeType: .email(emailPattern: "", length: data.length, nextPhoneLoginDate: nil, appleSignInAllowed: false, setup: true), nextType: nil, timeout: nil, termsOfService: nil) + pushControllerImpl?(codeController, true) + dismissCodeControllerImpl = { [weak codeController] in + codeController?.dismiss() + } + }, error: { [weak emailController] error in + emailController?.inProgress = false + + let text: String + switch error { + case .limitExceeded: + text = presentationData.strings.Login_CodeFloodError + case .generic, .codeExpired: + text = presentationData.strings.Login_UnknownError + case .timeout: + text = presentationData.strings.Login_NetworkError + } + + presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})])) + }, completed: { [weak emailController] in + emailController?.inProgress = false + })) + } + emailController.signInWithApple = { + + } + emailController.updateData(appleSignInAllowed: true) + pushControllerImpl?(emailController, true) + + dismissEmailControllerImpl = { [weak emailController] in + emailController?.dismiss() + } + } + return controller } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift index 918868ffa0..327ee55a34 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyIntroControllerNode.swift @@ -7,7 +7,7 @@ import TelegramCore import SwiftSignalKit import TelegramPresentationData import AccountContext -import AuthorizationUI +import AuthorizationUtils import AnimatedStickerNode import TelegramAnimatedStickerNode diff --git a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift index 51df47b9b7..c53a29f3a9 100644 --- a/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift +++ b/submodules/SolidRoundedButtonNode/Sources/SolidRoundedButtonNode.swift @@ -23,11 +23,15 @@ public final class SolidRoundedButtonTheme: Equatable { public let backgroundColor: UIColor public let backgroundColors: [UIColor] public let foregroundColor: UIColor + public let disabledBackgroundColor: UIColor? + public let disabledForegroundColor: UIColor? - public init(backgroundColor: UIColor, backgroundColors: [UIColor] = [], foregroundColor: UIColor) { + public init(backgroundColor: UIColor, backgroundColors: [UIColor] = [], foregroundColor: UIColor, disabledBackgroundColor: UIColor? = nil, disabledForegroundColor: UIColor? = nil) { self.backgroundColor = backgroundColor self.backgroundColors = backgroundColors self.foregroundColor = foregroundColor + self.disabledBackgroundColor = disabledBackgroundColor + self.disabledForegroundColor = disabledForegroundColor } public static func ==(lhs: SolidRoundedButtonTheme, rhs: SolidRoundedButtonTheme) -> Bool { @@ -40,6 +44,12 @@ public final class SolidRoundedButtonTheme: Equatable { if lhs.foregroundColor != rhs.foregroundColor { return false } + if lhs.disabledBackgroundColor != rhs.disabledBackgroundColor { + return false + } + if lhs.disabledForegroundColor != rhs.disabledForegroundColor { + return false + } return true } } @@ -86,6 +96,14 @@ public final class SolidRoundedButtonNode: ASDisplayNode { public var pressed: (() -> Void)? public var validLayout: CGFloat? + public var isEnabled: Bool = true { + didSet { + if self.isEnabled != oldValue { + self.updateColors(animated: true) + } + } + } + public var title: String? { didSet { if let width = self.validLayout { @@ -220,7 +238,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode { self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) self.buttonNode.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { + if let strongSelf = self, strongSelf.isEnabled { if highlighted { strongSelf.buttonBackgroundNode.layer.removeAnimation(forKey: "opacity") strongSelf.buttonBackgroundNode.alpha = 0.55 @@ -312,36 +330,20 @@ public final class SolidRoundedButtonNode: ASDisplayNode { } } - public func animateTitle(from title: String) { - let originalTitle = self.title ?? "" - self.title = title - + public func animateTitle(to title: String) { Queue.mainQueue().justDispatch { - if let snapshotView = self.titleNode.view.snapshotView(afterScreenUpdates: false) { - snapshotView.frame = self.titleNode.frame + if let snapshotView = self.view.snapshotView(afterScreenUpdates: false) { + snapshotView.frame = self.bounds - self.view.insertSubview(snapshotView, aboveSubview: self.titleNode.view) + self.view.addSubview(snapshotView) snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in snapshotView?.removeFromSuperview() }) - self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - - self.title = originalTitle } - } - } - - public func animateTitle(to title: String) { - if let snapshotView = self.titleNode.view.snapshotView(afterScreenUpdates: false) { - snapshotView.frame = self.titleNode.frame - - self.view.insertSubview(snapshotView, aboveSubview: self.titleNode.view) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - self.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.title = title + self.buttonBackgroundNode.backgroundColor = self.theme.disabledBackgroundColor + self.isEnabled = false } } @@ -459,40 +461,60 @@ public final class SolidRoundedButtonNode: ASDisplayNode { } } - self.buttonBackgroundNode.backgroundColor = theme.backgroundColor - if theme.backgroundColors.count > 1 { - self.buttonBackgroundNode.backgroundColor = nil - - var locations: [CGFloat] = [] - let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1) - for i in 0 ..< theme.backgroundColors.count { - locations.append(delta * CGFloat(i)) - } - self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: self.buttonHeight), colors: theme.backgroundColors, locations: locations, direction: .horizontal) - - if self.buttonBackgroundAnimationView == nil { - let buttonBackgroundAnimationView = UIImageView() - self.buttonBackgroundAnimationView = buttonBackgroundAnimationView - self.buttonBackgroundNode.view.addSubview(buttonBackgroundAnimationView) - } - - self.buttonBackgroundAnimationView?.image = self.buttonBackgroundNode.image - } else { - self.buttonBackgroundNode.image = nil - self.buttonBackgroundAnimationView?.image = nil - } - - self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: theme.foregroundColor) - self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: theme.foregroundColor) - self.iconNode.image = generateTintedImage(image: self.iconNode.image, color: theme.foregroundColor) self.animationNode?.customColor = theme.foregroundColor + self.updateColors(animated: false) + + self.updateShimmerParameters() + } + + func updateColors(animated: Bool) { + if animated { + if let snapshotView = self.view.snapshotView(afterScreenUpdates: false) { + self.view.addSubview(snapshotView) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + + if !self.isEnabled, let disabledBackgroundColor = self.theme.disabledBackgroundColor, let disabledForegroundColor = self.theme.disabledForegroundColor { + self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: disabledForegroundColor) + self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: disabledForegroundColor) + + self.buttonBackgroundNode.backgroundColor = disabledBackgroundColor + } else { + self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: self.theme.foregroundColor) + self.subtitleNode.attributedText = NSAttributedString(string: self.subtitle ?? "", font: Font.regular(14.0), textColor: self.theme.foregroundColor) + + self.buttonBackgroundNode.backgroundColor = theme.backgroundColor + if theme.backgroundColors.count > 1 { + self.buttonBackgroundNode.backgroundColor = nil + + var locations: [CGFloat] = [] + let delta = 1.0 / CGFloat(theme.backgroundColors.count - 1) + for i in 0 ..< theme.backgroundColors.count { + locations.append(delta * CGFloat(i)) + } + self.buttonBackgroundNode.image = generateGradientImage(size: CGSize(width: 200.0, height: self.buttonHeight), colors: theme.backgroundColors, locations: locations, direction: .horizontal) + + if self.buttonBackgroundAnimationView == nil { + let buttonBackgroundAnimationView = UIImageView() + self.buttonBackgroundAnimationView = buttonBackgroundAnimationView + self.buttonBackgroundNode.view.addSubview(buttonBackgroundAnimationView) + } + + self.buttonBackgroundAnimationView?.image = self.buttonBackgroundNode.image + } else { + self.buttonBackgroundNode.image = nil + self.buttonBackgroundAnimationView?.image = nil + } + } + if let width = self.validLayout { _ = self.updateLayout(width: width, transition: .immediate) } - - self.updateShimmerParameters() } public func sizeThatFits(_ constrainedSize: CGSize) -> CGSize { @@ -532,7 +554,7 @@ public final class SolidRoundedButtonNode: ASDisplayNode { transition.updateFrame(node: self.buttonNode, frame: buttonFrame) if self.title != self.titleNode.attributedText?.string { - self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: self.theme.foregroundColor) + self.titleNode.attributedText = NSAttributedString(string: self.title ?? "", font: self.font == .bold ? Font.semibold(self.fontSize) : Font.regular(self.fontSize), textColor: self.isEnabled ? self.theme.foregroundColor : (self.theme.disabledForegroundColor ?? self.theme.foregroundColor)) } let iconSize: CGSize @@ -595,7 +617,9 @@ public final class SolidRoundedButtonNode: ASDisplayNode { } @objc private func buttonPressed() { - self.pressed?() + if self.isEnabled { + self.pressed?() + } } public func transitionToProgress() { diff --git a/submodules/StickerPackPreviewUI/BUILD b/submodules/StickerPackPreviewUI/BUILD index 12812342de..f09cf1c31d 100644 --- a/submodules/StickerPackPreviewUI/BUILD +++ b/submodules/StickerPackPreviewUI/BUILD @@ -39,6 +39,7 @@ swift_library( "//submodules/TelegramUI/Components/AnimationCache:AnimationCache", "//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer", "//submodules/StickerPeekUI:StickerPeekUI", + "//submodules/Pasteboard:Pasteboard", ], visibility = [ "//visibility:public", diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift index 1d2cc13d0d..d56d9d2b35 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift @@ -14,6 +14,7 @@ import ShimmerEffect import EntityKeyboard import AnimationCache import MultiAnimationRenderer +import TextFormat private let nativeItemSize = 36.0 private let minItemsPerRow = 8 @@ -24,17 +25,14 @@ private let containerInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, ri class ItemLayout { let width: CGFloat let itemsCount: Int - let hasTitle: Bool let itemsPerRow: Int let visibleItemSize: CGFloat let horizontalSpacing: CGFloat - let itemTopOffset: CGFloat let height: CGFloat - init(width: CGFloat, itemsCount: Int, hasTitle: Bool) { + init(width: CGFloat, itemsCount: Int) { self.width = width self.itemsCount = itemsCount - self.hasTitle = hasTitle let itemHorizontalSpace = width - containerInsets.left - containerInsets.right self.itemsPerRow = max(minItemsPerRow, Int((itemHorizontalSpace + minSpacing) / (nativeItemSize + minSpacing))) @@ -45,8 +43,7 @@ class ItemLayout { let numRowsInGroup = (itemsCount + (self.itemsPerRow - 1)) / self.itemsPerRow - self.itemTopOffset = hasTitle ? 61.0 : 0.0 - self.height = itemTopOffset + CGFloat(numRowsInGroup) * visibleItemSize + CGFloat(max(0, numRowsInGroup - 1)) * verticalSpacing + self.height = CGFloat(numRowsInGroup) * visibleItemSize + CGFloat(max(0, numRowsInGroup - 1)) * verticalSpacing } func frame(itemIndex: Int) -> CGRect { @@ -56,7 +53,7 @@ class ItemLayout { return CGRect( origin: CGPoint( x: containerInsets.left + CGFloat(column) * (self.visibleItemSize + self.horizontalSpacing), - y: self.itemTopOffset + CGFloat(row) * (self.visibleItemSize + verticalSpacing) + y: CGFloat(row) * (self.visibleItemSize + verticalSpacing) ), size: CGSize( width: self.visibleItemSize, @@ -96,8 +93,8 @@ final class StickerPackEmojisItem: GridItem { self.isEmpty = isEmpty self.fillsRowWithDynamicHeight = { width in - let layout = ItemLayout(width: width, itemsCount: items.count, hasTitle: title != nil) - return layout.height + let layout = ItemLayout(width: width, itemsCount: items.count) + return layout.height + (title != nil ? 61.0 : 0.0) } } @@ -128,11 +125,13 @@ final class StickerPackEmojisItemNode: GridItemNode { private var visibleItemLayers: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemLayer] = [:] private var visibleItemPlaceholderViews: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemPlaceholderView] = [:] + private let containerNode: ASDisplayNode private let titleNode: ImmediateTextNode private let subtitleNode: ImmediateTextNode private let buttonNode: HighlightableButtonNode override init() { + self.containerNode = ASDisplayNode() self.titleNode = ImmediateTextNode() self.subtitleNode = ImmediateTextNode() self.buttonNode = HighlightableButtonNode(pointerStyle: nil) @@ -141,6 +140,7 @@ final class StickerPackEmojisItemNode: GridItemNode { super.init() + self.addSubnode(self.containerNode) self.addSubnode(self.titleNode) self.addSubnode(self.subtitleNode) self.addSubnode(self.buttonNode) @@ -191,6 +191,98 @@ final class StickerPackEmojisItemNode: GridItemNode { self?.standaloneShimmerEffect?.updateLayer() } self.boundsChangeTrackerLayer = boundsChangeTrackerLayer + + let gestureRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) + gestureRecognizer.longTap = { [weak self] point, _ in + guard let strongSelf = self else { + return + } + + if let (item, itemFrame) = strongSelf.item(atPoint: point), let file = item.itemFile { + var text = "." + var emojiAttribute: ChatTextInputTextCustomEmojiAttribute? + loop: for attribute in file.attributes { + switch attribute { + case let .CustomEmoji(_, displayText, packReference): + text = displayText + emojiAttribute = ChatTextInputTextCustomEmojiAttribute(stickerPack: packReference, fileId: file.fileId.id, file: file) + break loop + default: + break + } + } + + if let emojiAttribute = emojiAttribute { + strongSelf.item?.interaction.emojiLongPressed(text, emojiAttribute, strongSelf.containerNode, itemFrame) + } + } + } + self.containerNode.view.addGestureRecognizer(gestureRecognizer) + } + + @objc private func tapGesture(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + switch recognizer.state { + case .ended: + if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation, let (item, _) = self.item(atPoint: location), let file = item.itemFile { + if case .tap = gesture { + var text = "." + var emojiAttribute: ChatTextInputTextCustomEmojiAttribute? + loop: for attribute in file.attributes { + switch attribute { + case let .CustomEmoji(_, displayText, packReference): + text = displayText + emojiAttribute = ChatTextInputTextCustomEmojiAttribute(stickerPack: packReference, fileId: file.fileId.id, file: file) + break loop + default: + break + } + } + + if let emojiAttribute = emojiAttribute { + self.item?.interaction.emojiSelected(text, emojiAttribute) + } + } + } + default: + break + } + } + + private func item(atPoint point: CGPoint, extendedHitRange: Bool = false) -> (EmojiPagerContentComponent.Item, CGRect)? { + let localPoint = point + + var closestItem: (key: EmojiPagerContentComponent.View.ItemLayer.Key, distance: CGFloat)? + + for (key, itemLayer) in self.visibleItemLayers { + if extendedHitRange { + let position = CGPoint(x: itemLayer.frame.midX, y: itemLayer.frame.midY) + let distance = CGPoint(x: localPoint.x - position.x, y: localPoint.y - position.y) + let distance2 = distance.x * distance.x + distance.y * distance.y + if distance2 > pow(max(itemLayer.bounds.width, itemLayer.bounds.height), 2.0) { + continue + } + + if let closestItemValue = closestItem { + if closestItemValue.distance > distance2 { + closestItem = (key, distance2) + } + } else { + closestItem = (key, distance2) + } + } else { + if itemLayer.frame.contains(localPoint) { + return (itemLayer.item, itemLayer.frame) + } + } + } + + if let key = closestItem?.key { + if let itemLayer = self.visibleItemLayers[key] { + return (itemLayer.item, itemLayer.frame) + } + } + + return nil } private var size = CGSize() @@ -233,13 +325,15 @@ final class StickerPackEmojisItemNode: GridItemNode { var validIds = Set() let itemLayout: ItemLayout - if let current = self.itemLayout, current.width == self.size.width && current.itemsCount == items.count && current.hasTitle == (item.title != nil) { + if let current = self.itemLayout, current.width == self.size.width && current.itemsCount == items.count { itemLayout = current } else { - itemLayout = ItemLayout(width: self.size.width, itemsCount: items.count, hasTitle: item.title != nil) + itemLayout = ItemLayout(width: self.size.width, itemsCount: items.count) self.itemLayout = itemLayout } + self.containerNode.frame = CGRect(origin: CGPoint(x: 0.0, y: item.title != nil ? 61.0 : 0.0), size: CGSize(width: itemLayout.width, height: itemLayout.height)) + for index in 0 ..< items.count { let item = items[index] let itemId = EmojiPagerContentComponent.View.ItemLayer.Key( @@ -325,7 +419,7 @@ final class StickerPackEmojisItemNode: GridItemNode { } } ) - self.layer.addSublayer(itemLayer) + self.containerNode.layer.addSublayer(itemLayer) self.visibleItemLayers[itemId] = itemLayer } diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift index 81ac844056..9a0925950d 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewControllerNode.swift @@ -143,7 +143,7 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol super.init() - self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: false, addStickerPack: { _, _ in }, removeStickerPack: { _ in }) + self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: false, addStickerPack: { _, _ in }, removeStickerPack: { _ in }, emojiSelected: { _, _ in }, emojiLongPressed: { _, _, _, _ in }) self.backgroundColor = nil self.isOpaque = false diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift index 888afaad03..8849783d9b 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackPreviewGridItem.swift @@ -12,6 +12,7 @@ import TelegramAnimatedStickerNode import TelegramPresentationData import ShimmerEffect import StickerPeekUI +import TextFormat final class StickerPackPreviewInteraction { var previewedItem: StickerPreviewPeekItem? @@ -19,11 +20,15 @@ final class StickerPackPreviewInteraction { let addStickerPack: (StickerPackCollectionInfo, [StickerPackItem]) -> Void let removeStickerPack: (StickerPackCollectionInfo) -> Void + let emojiSelected: (String, ChatTextInputTextCustomEmojiAttribute) -> Void + let emojiLongPressed: (String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void - init(playAnimatedStickers: Bool, addStickerPack: @escaping (StickerPackCollectionInfo, [StickerPackItem]) -> Void, removeStickerPack: @escaping (StickerPackCollectionInfo) -> Void) { + init(playAnimatedStickers: Bool, addStickerPack: @escaping (StickerPackCollectionInfo, [StickerPackItem]) -> Void, removeStickerPack: @escaping (StickerPackCollectionInfo) -> Void, emojiSelected: @escaping (String, ChatTextInputTextCustomEmojiAttribute) -> Void, emojiLongPressed: @escaping (String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void) { self.playAnimatedStickers = playAnimatedStickers self.addStickerPack = addStickerPack self.removeStickerPack = removeStickerPack + self.emojiSelected = emojiSelected + self.emojiLongPressed = emojiLongPressed } } diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index ca95373e3a..4bf070365b 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -21,6 +21,7 @@ import PresentationDataUtils import StickerPeekUI import AnimationCache import MultiAnimationRenderer +import Pasteboard private enum StickerPackPreviewGridEntry: Comparable, Identifiable { case sticker(index: Int, stableId: Int, stickerItem: StickerPackItem?, isEmpty: Bool, isPremium: Bool, isLocked: Bool) @@ -145,7 +146,21 @@ private final class StickerPackContainer: ASDisplayNode { var onReady: () -> Void = {} var onError: () -> Void = {} - init(index: Int, context: AccountContext, presentationData: PresentationData, stickerPacks: [StickerPackReference], decideNextAction: @escaping (StickerPackContainer, StickerPackAction) -> StickerPackNextAction, requestDismiss: @escaping () -> Void, expandProgressUpdated: @escaping (StickerPackContainer, ContainedViewLayoutTransition, ContainedViewLayoutTransition) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, openMention: @escaping (String) -> Void, controller: StickerPackScreenImpl?) { + init( + index: Int, + context: AccountContext, + presentationData: PresentationData, + stickerPacks: [StickerPackReference], + decideNextAction: @escaping (StickerPackContainer, StickerPackAction) -> StickerPackNextAction, + requestDismiss: @escaping () -> Void, + expandProgressUpdated: @escaping (StickerPackContainer, ContainedViewLayoutTransition, ContainedViewLayoutTransition) -> Void, + presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, + sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, + sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, + longPressEmoji: ((String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void)?, + openMention: @escaping (String) -> Void, + controller: StickerPackScreenImpl?) + { self.index = index self.context = context self.controller = controller @@ -201,10 +216,16 @@ private final class StickerPackContainer: ASDisplayNode { var addStickerPackImpl: ((StickerPackCollectionInfo, [StickerPackItem]) -> Void)? var removeStickerPackImpl: ((StickerPackCollectionInfo) -> Void)? + var emojiSelectedImpl: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? + var emojiLongPressedImpl: ((String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void)? self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: true, addStickerPack: { info, items in addStickerPackImpl?(info, items) }, removeStickerPack: { info in removeStickerPackImpl?(info) + }, emojiSelected: { text, attribute in + emojiSelectedImpl?(text, attribute) + }, emojiLongPressed: { text, attribute, node, frame in + emojiLongPressedImpl?(text, attribute, node, frame) }) super.init() @@ -215,7 +236,6 @@ private final class StickerPackContainer: ASDisplayNode { self.addSubnode(self.actionAreaSeparatorNode) self.addSubnode(self.buttonNode) -// self.addSubnode(self.titleBackgroundnode) self.titleContainer.addSubnode(self.titleNode) self.addSubnode(self.titleContainer) self.addSubnode(self.titleSeparatorNode) @@ -395,6 +415,14 @@ private final class StickerPackContainer: ASDisplayNode { let _ = strongSelf.context.engine.stickers.removeStickerPackInteractively(id: info.id, option: .delete).start() } } + + emojiSelectedImpl = { text, attribute in + sendEmoji?(text, attribute) + } + + emojiLongPressedImpl = { text, attribute, node, frame in + longPressEmoji?(text, attribute, node, frame) + } } deinit { @@ -593,7 +621,7 @@ private final class StickerPackContainer: ASDisplayNode { return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in f(.default) - + UIPasteboard.general.string = text if let strongSelf = self { @@ -1017,13 +1045,13 @@ private final class StickerPackContainer: ASDisplayNode { if !self.currentStickerPacks.isEmpty { var packsHeight = 0.0 for stickerPack in currentStickerPacks { - let layout = ItemLayout(width: fillingWidth, itemsCount: stickerPack.1.count, hasTitle: true) - packsHeight += layout.height + let layout = ItemLayout(width: fillingWidth, itemsCount: stickerPack.1.count) + packsHeight += layout.height + 61.0 } contentHeight = packsHeight + 8.0 } else if let (info, items, _) = self.currentStickerPack { if info.id.namespace == Namespaces.ItemCollection.CloudEmojiPacks { - let layout = ItemLayout(width: fillingWidth, itemsCount: items.count, hasTitle: false) + let layout = ItemLayout(width: fillingWidth, itemsCount: items.count) contentHeight = layout.height } else { let rowCount = items.count / itemsPerRow + ((items.count % itemsPerRow) == 0 ? 0 : 1) @@ -1186,6 +1214,8 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { private let dismissed: () -> Void private let presentInGlobalOverlay: (ViewController, Any?) -> Void private let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? + private let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? + private let longPressEmoji: ((String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void)? private let openMention: (String) -> Void private let dimNode: ASDisplayNode @@ -1207,7 +1237,19 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { var onReady: () -> Void = {} var onError: () -> Void = {} - init(context: AccountContext, controller: StickerPackScreenImpl, stickerPacks: [StickerPackReference], initialSelectedStickerPackIndex: Int, modalProgressUpdated: @escaping (CGFloat, ContainedViewLayoutTransition) -> Void, dismissed: @escaping () -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, openMention: @escaping (String) -> Void) { + init( + context: AccountContext, + controller: StickerPackScreenImpl, + stickerPacks: [StickerPackReference], + initialSelectedStickerPackIndex: Int, + modalProgressUpdated: @escaping (CGFloat, ContainedViewLayoutTransition) -> Void, + dismissed: @escaping () -> Void, + presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, + sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?, + sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, + longPressEmoji: ((String, ChatTextInputTextCustomEmojiAttribute, ASDisplayNode, CGRect) -> Void)?, + openMention: @escaping (String) -> Void) + { self.context = context self.controller = controller self.presentationData = controller.presentationData @@ -1217,6 +1259,8 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { self.dismissed = dismissed self.presentInGlobalOverlay = presentInGlobalOverlay self.sendSticker = sendSticker + self.sendEmoji = sendEmoji + self.longPressEmoji = longPressEmoji self.openMention = openMention self.dimNode = ASDisplayNode() @@ -1327,7 +1371,7 @@ private final class StickerPackScreenNode: ViewControllerTracingNode { } } } - }, presentInGlobalOverlay: presentInGlobalOverlay, sendSticker: sendSticker, openMention: openMention, controller: self.controller) + }, presentInGlobalOverlay: presentInGlobalOverlay, sendSticker: self.sendSticker, sendEmoji: self.sendEmoji, longPressEmoji: self.longPressEmoji, openMention: self.openMention, controller: self.controller) container.onReady = { [weak self] in self?.onReady() } @@ -1518,6 +1562,7 @@ public final class StickerPackScreenImpl: ViewController { private let initialSelectedStickerPackIndex: Int fileprivate weak var parentNavigationController: NavigationController? private let sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? + private let sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? private var controllerNode: StickerPackScreenNode { return self.displayNode as! StickerPackScreenNode @@ -1539,13 +1584,23 @@ public final class StickerPackScreenImpl: ViewController { let animationCache: AnimationCache let animationRenderer: MultiAnimationRenderer - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, stickerPacks: [StickerPackReference], selectedStickerPackIndex: Int = 0, parentNavigationController: NavigationController? = nil, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil, actionPerformed: (([(StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction)]) -> Void)? = nil) { + public init( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + stickerPacks: [StickerPackReference], + selectedStickerPackIndex: Int = 0, + parentNavigationController: NavigationController? = nil, + sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil, + sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)?, + actionPerformed: (([(StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction)]) -> Void)? = nil + ) { self.context = context self.presentationData = updatedPresentationData?.initial ?? context.sharedContext.currentPresentationData.with { $0 } self.stickerPacks = stickerPacks self.initialSelectedStickerPackIndex = selectedStickerPackIndex self.parentNavigationController = parentNavigationController self.sendSticker = sendSticker + self.sendEmoji = sendEmoji self.actionPerformed = actionPerformed self.animationCache = AnimationCacheImpl(basePath: context.account.postbox.mediaBox.basePath + "/animation-cache", allocateTempFile: { @@ -1605,6 +1660,46 @@ public final class StickerPackScreenImpl: ViewController { return false } } + }, sendEmoji: self.sendEmoji.flatMap { [weak self] sendEmoji in + return { text, attribute in + sendEmoji(text, attribute) + self?.controllerNode.dismiss() + } + }, longPressEmoji: { [weak self] text, attribute, node, frame in + guard let strongSelf = self else { + return + } + + var actions: [ContextMenuAction] = [] + + actions.append(ContextMenuAction(content: .text(title: strongSelf.presentationData.strings.Conversation_ContextMenuCopy, accessibilityLabel: strongSelf.presentationData.strings.Conversation_ContextMenuCopy), action: { [weak self] in + storeMessageTextInPasteboard( + text, + entities: [ + MessageTextEntity( + range: 0 ..< (text as NSString).length, + type: .CustomEmoji( + stickerPack: attribute.stickerPack, + fileId: attribute.fileId + ) + ) + ] + ) + + if let strongSelf = self, let file = attribute.file { + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .sticker(context: strongSelf.context, file: file, title: nil, text: presentationData.strings.Conversation_EmojiCopied, undoText: nil, customAction: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .current) + } + })) + + let contextMenuController = ContextMenuController(actions: actions) + strongSelf.present(contextMenuController, in: .window(.root), with: ContextMenuControllerPresentationArguments(sourceNodeAndRect: { [weak self] in + if let strongSelf = self { + return (node, frame.insetBy(dx: -40.0, dy: 0.0), strongSelf.controllerNode, strongSelf.controllerNode.view.bounds) + } else { + return nil + } + })) }, openMention: { [weak self] mention in guard let strongSelf = self else { return @@ -1718,16 +1813,29 @@ public enum StickerPackScreenPerformedAction { case remove(positionInList: Int) } -public func StickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: StickerPackPreviewControllerMode = .default, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], parentNavigationController: NavigationController? = nil, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil, actionPerformed: (([(StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction)]) -> Void)? = nil, dismissed: (() -> Void)? = nil) -> ViewController { - //let stickerPacks = [mainStickerPack] - let controller = StickerPackScreenImpl(context: context, stickerPacks: stickerPacks, selectedStickerPackIndex: stickerPacks.firstIndex(of: mainStickerPack) ?? 0, parentNavigationController: parentNavigationController, sendSticker: sendSticker, actionPerformed: actionPerformed) +public func StickerPackScreen( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, + mode: StickerPackPreviewControllerMode = .default, + mainStickerPack: StickerPackReference, + stickerPacks: [StickerPackReference], + parentNavigationController: NavigationController? = nil, + sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)? = nil, + sendEmoji: ((String, ChatTextInputTextCustomEmojiAttribute) -> Void)? = nil, + actionPerformed: (([(StickerPackCollectionInfo, [StickerPackItem], StickerPackScreenPerformedAction)]) -> Void)? = nil, + dismissed: (() -> Void)? = nil) -> ViewController +{ + let controller = StickerPackScreenImpl( + context: context, + stickerPacks: stickerPacks, + selectedStickerPackIndex: stickerPacks.firstIndex(of: mainStickerPack) ?? 0, + parentNavigationController: parentNavigationController, + sendSticker: sendSticker, + sendEmoji: sendEmoji, + actionPerformed: actionPerformed + ) controller.dismissed = dismissed return controller - -// let controller = StickerPackPreviewController(context: context, updatedPresentationData: updatedPresentationData, stickerPack: mainStickerPack, mode: mode, parentNavigationController: parentNavigationController, actionPerformed: actionPerformed) -// controller.dismissed = dismissed -// controller.sendSticker = sendSticker -// return controller } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 0202dce082..2fa794df15 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -595,6 +595,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } dict[512535275] = { return Api.PostAddress.parse_postAddress($0) } dict[1958953753] = { return Api.PremiumGiftOption.parse_premiumGiftOption($0) } + dict[-1225711938] = { return Api.PremiumSubscriptionOption.parse_premiumSubscriptionOption($0) } dict[1124062251] = { return Api.PrivacyKey.parse_privacyKeyAddedByPhone($0) } dict[1343122938] = { return Api.PrivacyKey.parse_privacyKeyChatInvite($0) } dict[1777096355] = { return Api.PrivacyKey.parse_privacyKeyForwards($0) } @@ -953,7 +954,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[415997816] = { return Api.help.InviteText.parse_inviteText($0) } dict[-1600596305] = { return Api.help.PassportConfig.parse_passportConfig($0) } dict[-1078332329] = { return Api.help.PassportConfig.parse_passportConfigNotModified($0) } - dict[-1974518743] = { return Api.help.PremiumPromo.parse_premiumPromo($0) } + dict[1395946908] = { return Api.help.PremiumPromo.parse_premiumPromo($0) } dict[-1942390465] = { return Api.help.PromoData.parse_promoData($0) } dict[-1728664459] = { return Api.help.PromoData.parse_promoDataEmpty($0) } dict[235081943] = { return Api.help.RecentMeUrls.parse_recentMeUrls($0) } @@ -1497,6 +1498,8 @@ public extension Api { _1.serialize(buffer, boxed) case let _1 as Api.PremiumGiftOption: _1.serialize(buffer, boxed) + case let _1 as Api.PremiumSubscriptionOption: + _1.serialize(buffer, boxed) case let _1 as Api.PrivacyKey: _1.serialize(buffer, boxed) case let _1 as Api.PrivacyRule: diff --git a/submodules/TelegramApi/Sources/Api16.swift b/submodules/TelegramApi/Sources/Api16.swift index 2c3236c9e6..81b23ee6e2 100644 --- a/submodules/TelegramApi/Sources/Api16.swift +++ b/submodules/TelegramApi/Sources/Api16.swift @@ -652,6 +652,62 @@ public extension Api { } } +public extension Api { + enum PremiumSubscriptionOption: TypeConstructorDescription { + case premiumSubscriptionOption(flags: Int32, months: Int32, currency: String, amount: Int64, botUrl: String, storeProduct: String?) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .premiumSubscriptionOption(let flags, let months, let currency, let amount, let botUrl, let storeProduct): + if boxed { + buffer.appendInt32(-1225711938) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(months, buffer: buffer, boxed: false) + serializeString(currency, buffer: buffer, boxed: false) + serializeInt64(amount, buffer: buffer, boxed: false) + serializeString(botUrl, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeString(storeProduct!, buffer: buffer, boxed: false)} + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .premiumSubscriptionOption(let flags, let months, let currency, let amount, let botUrl, let storeProduct): + return ("premiumSubscriptionOption", [("flags", String(describing: flags)), ("months", String(describing: months)), ("currency", String(describing: currency)), ("amount", String(describing: amount)), ("botUrl", String(describing: botUrl)), ("storeProduct", String(describing: storeProduct))]) + } + } + + public static func parse_premiumSubscriptionOption(_ reader: BufferReader) -> PremiumSubscriptionOption? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: String? + _3 = parseString(reader) + var _4: Int64? + _4 = reader.readInt64() + var _5: String? + _5 = parseString(reader) + var _6: String? + if Int(_1!) & Int(1 << 0) != 0 {_6 = parseString(reader) } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + let _c5 = _5 != nil + let _c6 = (Int(_1!) & Int(1 << 0) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.PremiumSubscriptionOption.premiumSubscriptionOption(flags: _1!, months: _2!, currency: _3!, amount: _4!, botUrl: _5!, storeProduct: _6) + } + else { + return nil + } + } + + } +} public extension Api { enum PrivacyKey: TypeConstructorDescription { case privacyKeyAddedByPhone diff --git a/submodules/TelegramApi/Sources/Api24.swift b/submodules/TelegramApi/Sources/Api24.swift index d60d7e3085..f3e8113f14 100644 --- a/submodules/TelegramApi/Sources/Api24.swift +++ b/submodules/TelegramApi/Sources/Api24.swift @@ -414,13 +414,13 @@ public extension Api.help { } public extension Api.help { enum PremiumPromo: TypeConstructorDescription { - case premiumPromo(statusText: String, statusEntities: [Api.MessageEntity], videoSections: [String], videos: [Api.Document], currency: String, monthlyAmount: Int64, users: [Api.User]) + case premiumPromo(statusText: String, statusEntities: [Api.MessageEntity], videoSections: [String], videos: [Api.Document], periodOptions: [Api.PremiumSubscriptionOption], users: [Api.User]) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let currency, let monthlyAmount, let users): + case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let periodOptions, let users): if boxed { - buffer.appendInt32(-1974518743) + buffer.appendInt32(1395946908) } serializeString(statusText, buffer: buffer, boxed: false) buffer.appendInt32(481674261) @@ -438,8 +438,11 @@ public extension Api.help { for item in videos { item.serialize(buffer, true) } - serializeString(currency, buffer: buffer, boxed: false) - serializeInt64(monthlyAmount, buffer: buffer, boxed: false) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(periodOptions.count)) + for item in periodOptions { + item.serialize(buffer, true) + } buffer.appendInt32(481674261) buffer.appendInt32(Int32(users.count)) for item in users { @@ -451,8 +454,8 @@ public extension Api.help { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let currency, let monthlyAmount, let users): - return ("premiumPromo", [("statusText", String(describing: statusText)), ("statusEntities", String(describing: statusEntities)), ("videoSections", String(describing: videoSections)), ("videos", String(describing: videos)), ("currency", String(describing: currency)), ("monthlyAmount", String(describing: monthlyAmount)), ("users", String(describing: users))]) + case .premiumPromo(let statusText, let statusEntities, let videoSections, let videos, let periodOptions, let users): + return ("premiumPromo", [("statusText", String(describing: statusText)), ("statusEntities", String(describing: statusEntities)), ("videoSections", String(describing: videoSections)), ("videos", String(describing: videos)), ("periodOptions", String(describing: periodOptions)), ("users", String(describing: users))]) } } @@ -471,13 +474,13 @@ public extension Api.help { if let _ = reader.readInt32() { _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Document.self) } - var _5: String? - _5 = parseString(reader) - var _6: Int64? - _6 = reader.readInt64() - var _7: [Api.User]? + var _5: [Api.PremiumSubscriptionOption]? if let _ = reader.readInt32() { - _7 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumSubscriptionOption.self) + } + var _6: [Api.User]? + if let _ = reader.readInt32() { + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) } let _c1 = _1 != nil let _c2 = _2 != nil @@ -485,9 +488,8 @@ public extension Api.help { let _c4 = _4 != nil let _c5 = _5 != nil let _c6 = _6 != nil - let _c7 = _7 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { - return Api.help.PremiumPromo.premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, currency: _5!, monthlyAmount: _6!, users: _7!) + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.help.PremiumPromo.premiumPromo(statusText: _1!, statusEntities: _2!, videoSections: _3!, videos: _4!, periodOptions: _5!, users: _6!) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index fda72e0ed6..5375b5f810 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -5383,13 +5383,13 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func reportReaction(peer: Api.InputPeer, id: Int32, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func reportReaction(peer: Api.InputPeer, id: Int32, reactionPeer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1631726152) + buffer.appendInt32(1063567478) peer.serialize(buffer, true) serializeInt32(id, buffer: buffer, boxed: false) - userId.serialize(buffer, true) - return (FunctionDescription(name: "messages.reportReaction", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("userId", String(describing: userId))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + reactionPeer.serialize(buffer, true) + return (FunctionDescription(name: "messages.reportReaction", parameters: [("peer", String(describing: peer)), ("id", String(describing: id)), ("reactionPeer", String(describing: reactionPeer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in let reader = BufferReader(buffer) var result: Api.Bool? if let signature = reader.readInt32() { @@ -5432,14 +5432,15 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func requestSimpleWebView(flags: Int32, bot: Api.InputUser, url: String, themeParams: Api.DataJSON?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func requestSimpleWebView(flags: Int32, bot: Api.InputUser, url: String, themeParams: Api.DataJSON?, platform: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1790652275) + buffer.appendInt32(698084494) serializeInt32(flags, buffer: buffer, boxed: false) bot.serialize(buffer, true) serializeString(url, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {themeParams!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.requestSimpleWebView", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("themeParams", String(describing: themeParams))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SimpleWebViewResult? in + serializeString(platform, buffer: buffer, boxed: false) + return (FunctionDescription(name: "messages.requestSimpleWebView", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("themeParams", String(describing: themeParams)), ("platform", String(describing: platform))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SimpleWebViewResult? in let reader = BufferReader(buffer) var result: Api.SimpleWebViewResult? if let signature = reader.readInt32() { @@ -5469,18 +5470,19 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func requestWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, replyToMsgId: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func requestWebView(flags: Int32, peer: Api.InputPeer, bot: Api.InputUser, url: String?, startParam: String?, themeParams: Api.DataJSON?, platform: String, replyToMsgId: Int32?, sendAs: Api.InputPeer?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-1850648527) + buffer.appendInt32(-58219204) serializeInt32(flags, buffer: buffer, boxed: false) peer.serialize(buffer, true) bot.serialize(buffer, true) if Int(flags) & Int(1 << 1) != 0 {serializeString(url!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 3) != 0 {serializeString(startParam!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 2) != 0 {themeParams!.serialize(buffer, true)} + serializeString(platform, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(replyToMsgId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 13) != 0 {sendAs!.serialize(buffer, true)} - return (FunctionDescription(name: "messages.requestWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("startParam", String(describing: startParam)), ("themeParams", String(describing: themeParams)), ("replyToMsgId", String(describing: replyToMsgId)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in + return (FunctionDescription(name: "messages.requestWebView", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("bot", String(describing: bot)), ("url", String(describing: url)), ("startParam", String(describing: startParam)), ("themeParams", String(describing: themeParams)), ("platform", String(describing: platform)), ("replyToMsgId", String(describing: replyToMsgId)), ("sendAs", String(describing: sendAs))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.WebViewResult? in let reader = BufferReader(buffer) var result: Api.WebViewResult? if let signature = reader.readInt32() { diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index 31d7d3b419..e3b9e22ff6 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -41,8 +41,7 @@ private func presentLiveLocationController(context: AccountContext, peerId: Peer }, callPeer: { _, _ in }, enqueueMessage: { message in let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() - }, sendSticker: nil, - setupTemporaryHiddenMedia: { _, _, _ in + }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in })) } diff --git a/submodules/TelegramCore/Sources/Authorization.swift b/submodules/TelegramCore/Sources/Authorization.swift index 0f27e8431a..75b6700c8e 100644 --- a/submodules/TelegramCore/Sources/Authorization.swift +++ b/submodules/TelegramCore/Sources/Authorization.swift @@ -322,6 +322,31 @@ public enum AuthorizationEmailVerificationError { case timeout } +public struct ChangeLoginEmailData: Equatable { + public let email: String + public let length: Int32 +} + +public func sendLoginEmailChangeCode(account: Account, email: String) -> Signal { + return account.network.request(Api.functions.account.sendVerifyEmailCode(purpose: .emailVerifyPurposeLoginChange, email: email), automaticFloodWait: false) + |> `catch` { error -> Signal in + let errorDescription = error.errorDescription ?? "" + if errorDescription.hasPrefix("FLOOD_WAIT") { + return .fail(.limitExceeded) + } else if errorDescription == "CODE_HASH_EXPIRED" || errorDescription == "PHONE_CODE_EXPIRED" { + return .fail(.codeExpired) + } else { + return .fail(.generic) + } + } + |> map { result -> ChangeLoginEmailData in + switch result { + case let .sentEmailCode(_, length): + return ChangeLoginEmailData(email: email, length: length) + } + } +} + public func sendLoginEmailCode(account: UnauthorizedAccount, email: String) -> Signal { return account.postbox.transaction { transaction -> Signal in if let state = transaction.getState() as? UnauthorizedAccountState { diff --git a/submodules/TelegramCore/Sources/State/ManagedPremiumPromoConfigurationUpdates.swift b/submodules/TelegramCore/Sources/State/ManagedPremiumPromoConfigurationUpdates.swift index 5355396ad5..d09cddb7a4 100644 --- a/submodules/TelegramCore/Sources/State/ManagedPremiumPromoConfigurationUpdates.swift +++ b/submodules/TelegramCore/Sources/State/ManagedPremiumPromoConfigurationUpdates.swift @@ -19,7 +19,7 @@ func updatePremiumPromoConfigurationOnce(postbox: Postbox, network: Network) -> return .complete() } return postbox.transaction { transaction -> Void in - if case let .premiumPromo(_, _, _, _, _, _, apiUsers) = result { + if case let .premiumPromo(_, _, _, _, _, apiUsers) = result { let users = apiUsers.map { TelegramUser(user: $0) } updatePeers(transaction: transaction, peers: users, update: { current, updated -> Peer in if let updated = updated as? TelegramUser { @@ -65,11 +65,10 @@ private func updatePremiumPromoConfiguration(transaction: Transaction, _ f: (Pre private extension PremiumPromoConfiguration { init(apiPremiumPromo: Api.help.PremiumPromo) { switch apiPremiumPromo { - case let .premiumPromo(statusText, statusEntities, videoSections, videoFiles, currency, monthlyAmount, _): + case let .premiumPromo(statusText, statusEntities, videoSections, videoFiles, options, _): self.status = statusText self.statusEntities = messageTextEntitiesFromApiEntities(statusEntities) - self.currency = currency - self.monthlyAmount = monthlyAmount + var videos: [String: TelegramMediaFile] = [:] for (key, document) in zip(videoSections, videoFiles) { if let file = telegramMediaFileFromApiDocument(document) { @@ -77,6 +76,14 @@ private extension PremiumPromoConfiguration { } } self.videos = videos + + var productOptions: [PremiumProductOption] = [] + for option in options { + if case let .premiumSubscriptionOption(_, months, currency, amount, botUrl, storeProduct) = option { + productOptions.append(PremiumProductOption(months: months, currency: currency, amount: amount, botUrl: botUrl, storeProductId: storeProduct)) + } + } + self.premiumProductOptions = productOptions } } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_PremiumPromoConfiguration.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PremiumPromoConfiguration.swift index 97f796b54f..de3e960f3e 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_PremiumPromoConfiguration.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PremiumPromoConfiguration.swift @@ -4,10 +4,9 @@ import Postbox public struct PremiumPromoConfiguration: Codable, Equatable { enum CodingKeys: String, CodingKey { case status - case currency - case monthlyAmount case statusEntities case videos + case premiumProductOptions } private struct DictionaryPair: Codable { @@ -34,23 +33,56 @@ public struct PremiumPromoConfiguration: Codable, Equatable { } } + public struct PremiumProductOption: Codable, Equatable { + public let months: Int32 + public let currency: String + public let amount: Int64 + public let botUrl: String + public let storeProductId: String? + + public init(months: Int32, currency: String, amount: Int64, botUrl: String, storeProductId: String?) { + self.months = months + self.currency = currency + self.amount = amount + self.botUrl = botUrl + self.storeProductId = storeProductId + } + + public init(decoder: PostboxDecoder) { + self.months = decoder.decodeInt32ForKey("months", orElse: 0) + self.currency = decoder.decodeStringForKey("currency", orElse: "") + self.amount = decoder.decodeInt64ForKey("amount", orElse: 0) + self.botUrl = decoder.decodeStringForKey("botUrl", orElse: "") + self.storeProductId = decoder.decodeOptionalStringForKey("storeProductId") + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt32(self.months, forKey: "months") + encoder.encodeString(self.currency, forKey: "currency") + encoder.encodeInt64(self.amount, forKey: "amount") + encoder.encodeString(self.botUrl, forKey: "botUrl") + if let storeProductId = self.storeProductId { + encoder.encodeString(storeProductId, forKey: "storeProductId") + } else { + encoder.encodeNil(forKey: "storeProductId") + } + } + } + public var status: String public var statusEntities: [MessageTextEntity] public var videos: [String: TelegramMediaFile] - - public var currency: String - public var monthlyAmount: Int64 + public var premiumProductOptions: [PremiumProductOption] public static var defaultValue: PremiumPromoConfiguration { - return PremiumPromoConfiguration(status: "", statusEntities: [], videos: [:], currency: "", monthlyAmount: 0) + return PremiumPromoConfiguration(status: "", statusEntities: [], videos: [:], premiumProductOptions: []) } - init(status: String, statusEntities: [MessageTextEntity], videos: [String: TelegramMediaFile], currency: String, monthlyAmount: Int64) { + init(status: String, statusEntities: [MessageTextEntity], videos: [String: TelegramMediaFile], premiumProductOptions: [PremiumProductOption]) { self.status = status - self.currency = currency - self.monthlyAmount = monthlyAmount self.statusEntities = statusEntities self.videos = videos + self.premiumProductOptions = premiumProductOptions } public init(from decoder: Decoder) throws { @@ -66,9 +98,7 @@ public struct PremiumPromoConfiguration: Codable, Equatable { } self.videos = videos - self.currency = try container.decode(String.self, forKey: .currency) - self.monthlyAmount = try container.decode(Int64.self, forKey: .monthlyAmount) - + self.premiumProductOptions = try container.decode([PremiumProductOption].self, forKey: .premiumProductOptions) } public func encode(to encoder: Encoder) throws { @@ -76,13 +106,13 @@ public struct PremiumPromoConfiguration: Codable, Equatable { try container.encode(self.status, forKey: .status) try container.encode(self.statusEntities, forKey: .statusEntities) - try container.encode(self.currency, forKey: .currency) - try container.encode(self.monthlyAmount, forKey: .monthlyAmount) - + var pairs: [DictionaryPair] = [] for (key, file) in self.videos { pairs.append(DictionaryPair(key, value: file)) } try container.encode(pairs, forKey: .videos) + + try container.encode(self.premiumProductOptions, forKey: .premiumProductOptions) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift index 74b2e3d28d..bbd3dc740c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/BotWebView.swift @@ -4,6 +4,12 @@ import SwiftSignalKit import TelegramApi import MtProtoKit +#if os(macOS) +private let botWebViewPlatform = "macos" +#else +private let botWebViewPlatform = "ios" +#endif + public enum RequestSimpleWebViewError { case generic } @@ -22,7 +28,7 @@ func _internal_requestSimpleWebView(postbox: Postbox, network: Network, botId: P if let _ = serializedThemeParams { flags |= (1 << 0) } - return network.request(Api.functions.messages.requestSimpleWebView(flags: flags, bot: inputUser, url: url, themeParams: serializedThemeParams)) + return network.request(Api.functions.messages.requestSimpleWebView(flags: flags, bot: inputUser, url: url, themeParams: serializedThemeParams, platform: botWebViewPlatform)) |> mapError { _ -> RequestSimpleWebViewError in return .generic } @@ -125,7 +131,7 @@ func _internal_requestWebView(postbox: Postbox, network: Network, stateManager: // if _ { // flags |= (1 << 13) // } - return network.request(Api.functions.messages.requestWebView(flags: flags, peer: inputPeer, bot: inputBot, url: url, startParam: payload, themeParams: serializedThemeParams, replyToMsgId: replyToMsgId, sendAs: nil)) + return network.request(Api.functions.messages.requestWebView(flags: flags, peer: inputPeer, bot: inputBot, url: url, startParam: payload, themeParams: serializedThemeParams, platform: botWebViewPlatform, replyToMsgId: replyToMsgId, sendAs: nil)) |> mapError { _ -> RequestWebViewError in return .generic } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift index b8a0a66f18..49d49c607c 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/ReportPeer.swift @@ -171,11 +171,11 @@ func _internal_reportPeerMessages(account: Account, messageIds: [MessageId], rea } func _internal_reportPeerReaction(account: Account, authorId: PeerId, messageId: MessageId) -> Signal { - return account.postbox.transaction { transaction -> (Api.InputPeer, Api.InputUser)? in + return account.postbox.transaction { transaction -> (Api.InputPeer, Api.InputPeer)? in guard let peer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else { return nil } - guard let author = transaction.getPeer(authorId).flatMap(apiInputUser) else { + guard let author = transaction.getPeer(authorId).flatMap(apiInputPeer) else { return nil } return (peer, author) @@ -184,7 +184,7 @@ func _internal_reportPeerReaction(account: Account, authorId: PeerId, messageId: guard let (inputPeer, inputUser) = inputData else { return .complete() } - return account.network.request(Api.functions.messages.reportReaction(peer: inputPeer, id: messageId.id, userId: inputUser)) + return account.network.request(Api.functions.messages.reportReaction(peer: inputPeer, id: messageId.id, reactionPeer: inputUser)) |> `catch` { _ -> Signal in return .single(.boolFalse) } diff --git a/submodules/TelegramNotices/Sources/Notices.swift b/submodules/TelegramNotices/Sources/Notices.swift index 85d0b458e1..33fc816bfa 100644 --- a/submodules/TelegramNotices/Sources/Notices.swift +++ b/submodules/TelegramNotices/Sources/Notices.swift @@ -163,6 +163,7 @@ private enum ApplicationSpecificGlobalNotice: Int32 { case sharedMediaScrollingTooltip = 29 case sharedMediaFastScrollingTooltip = 30 case forcedPasswordSetup = 31 + case emojiTooltip = 32 var key: ValueBoxKey { let v = ValueBoxKey(length: 4) @@ -339,6 +340,10 @@ private struct ApplicationSpecificNoticeKeys { static func sharedMediaFastScrollingTooltip() -> NoticeEntryKey { return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.sharedMediaFastScrollingTooltip.key) } + + static func emojiTooltip() -> NoticeEntryKey { + return NoticeEntryKey(namespace: noticeNamespace(namespace: globalNamespace), key: ApplicationSpecificGlobalNotice.emojiTooltip.key) + } } public struct ApplicationSpecificNotice { @@ -953,6 +958,30 @@ public struct ApplicationSpecificNotice { } } + public static func getEmojiTooltip(accountManager: AccountManager) -> Signal { + return accountManager.transaction { transaction -> Int32 in + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.emojiTooltip())?.get(ApplicationSpecificCounterNotice.self) { + return value.value + } else { + return 0 + } + } + } + + public static func incrementEmojiTooltip(accountManager: AccountManager, count: Int32 = 1) -> Signal { + return accountManager.transaction { transaction -> Void in + var currentValue: Int32 = 0 + if let value = transaction.getNotice(ApplicationSpecificNoticeKeys.emojiTooltip())?.get(ApplicationSpecificCounterNotice.self) { + currentValue = value.value + } + currentValue += count + + if let entry = CodableEntry(ApplicationSpecificCounterNotice(value: currentValue)) { + transaction.setNotice(ApplicationSpecificNoticeKeys.emojiTooltip(), entry) + } + } + } + public static func dismissedTrendingStickerPacks(accountManager: AccountManager) -> Signal<[Int64]?, NoError> { return accountManager.noticeEntry(key: ApplicationSpecificNoticeKeys.dismissedTrendingStickerPacks()) |> map { view -> [Int64]? in diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index f07a5d5ebb..107eb2c849 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -2160,7 +2160,7 @@ public final class EmojiPagerContentComponent: Component { case featured } - let item: Item + public let item: Item private let content: ItemContent private let placeholderColor: UIColor diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Menu/LoginEmail.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Settings/Menu/LoginEmail.imageset/Contents.json new file mode 100644 index 0000000000..e6cdd18a58 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/Menu/LoginEmail.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "LoginEmail.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Settings/Menu/LoginEmail.imageset/LoginEmail.pdf b/submodules/TelegramUI/Images.xcassets/Settings/Menu/LoginEmail.imageset/LoginEmail.pdf new file mode 100644 index 0000000000..a46486c153 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Settings/Menu/LoginEmail.imageset/LoginEmail.pdf @@ -0,0 +1,122 @@ +%PDF-1.7 + +1 0 obj + << >> +endobj + +2 0 obj + << /Length 3 0 R >> +stream +/DeviceRGB CS +/DeviceRGB cs +q +1.000000 0.000000 -0.000000 1.000000 0.000000 0.000000 cm +0.345098 0.337255 0.839216 scn +0.000000 18.799999 m +0.000000 22.720367 0.000000 24.680552 0.762954 26.177933 c +1.434068 27.495068 2.504932 28.565931 3.822066 29.237045 c +5.319448 30.000000 7.279633 30.000000 11.200000 30.000000 c +18.799999 30.000000 l +22.720367 30.000000 24.680552 30.000000 26.177933 29.237045 c +27.495068 28.565931 28.565931 27.495068 29.237045 26.177933 c +30.000000 24.680552 30.000000 22.720367 30.000000 18.799999 c +30.000000 11.200001 l +30.000000 7.279633 30.000000 5.319448 29.237045 3.822067 c +28.565931 2.504932 27.495068 1.434069 26.177933 0.762955 c +24.680552 0.000000 22.720367 0.000000 18.799999 0.000000 c +11.200000 0.000000 l +7.279633 0.000000 5.319448 0.000000 3.822066 0.762955 c +2.504932 1.434069 1.434068 2.504932 0.762954 3.822067 c +0.000000 5.319448 0.000000 7.279633 0.000000 11.200001 c +0.000000 18.799999 l +h +f +n +Q +q +1.000000 0.000000 -0.000000 1.000000 5.201416 5.201416 cm +1.000000 1.000000 1.000000 scn +0.000000 9.798596 m +0.000000 15.210198 4.386970 19.597168 9.798572 19.597168 c +15.210174 19.597168 19.597141 15.210198 19.597141 9.798596 c +19.597141 7.536692 l +19.597141 5.872736 18.248240 4.523834 16.584286 4.523834 c +15.334453 4.523834 14.262368 5.284863 13.806146 6.368805 c +12.838721 5.239468 11.402221 4.523834 9.798572 4.523834 c +6.885401 4.523834 4.523810 6.885427 4.523810 9.798597 c +4.523810 12.711767 6.885401 15.073359 9.798572 15.073359 c +11.278581 15.073359 12.616220 14.463821 13.574155 13.482087 c +13.606840 13.866659 13.929350 14.168596 14.322381 14.168596 c +14.737121 14.168596 15.073334 13.832384 15.073334 13.417645 c +15.073334 9.798597 l +15.073334 7.536693 l +15.073334 6.702216 15.749810 6.025740 16.584286 6.025740 c +17.418760 6.025740 18.095238 6.702215 18.095238 7.536692 c +18.095238 9.798596 l +18.095238 14.380720 14.380694 18.095263 9.798572 18.095263 c +5.216448 18.095263 1.501905 14.380720 1.501905 9.798596 c +1.501905 9.710638 1.503271 9.623017 1.505982 9.535753 c +1.644797 5.075261 5.304389 1.501930 9.798572 1.501930 c +11.100951 1.501930 12.331239 1.801481 13.426208 2.334747 c +13.799078 2.516340 14.248561 2.361280 14.430154 1.988409 c +14.611748 1.615540 14.456687 1.166056 14.083817 0.984463 c +12.788459 0.353603 11.333742 0.000027 9.798572 0.000027 c +4.386970 0.000027 0.000000 4.386994 0.000000 9.798596 c +h +9.798572 13.571454 m +11.882263 13.571454 13.571428 11.882288 13.571428 9.798597 c +13.571428 7.714905 11.882263 6.025740 9.798572 6.025740 c +7.714880 6.025740 6.025714 7.714905 6.025714 9.798597 c +6.025714 11.882288 7.714880 13.571454 9.798572 13.571454 c +h +f* +n +Q + +endstream +endobj + +3 0 obj + 2642 +endobj + +4 0 obj + << /Annots [] + /Type /Page + /MediaBox [ 0.000000 0.000000 30.000000 30.000000 ] + /Resources 1 0 R + /Contents 2 0 R + /Parent 5 0 R + >> +endobj + +5 0 obj + << /Kids [ 4 0 R ] + /Count 1 + /Type /Pages + >> +endobj + +6 0 obj + << /Pages 5 0 R + /Type /Catalog + >> +endobj + +xref +0 7 +0000000000 65535 f +0000000010 00000 n +0000000034 00000 n +0000002732 00000 n +0000002755 00000 n +0000002928 00000 n +0000003002 00000 n +trailer +<< /ID [ (some) (id) ] + /Root 6 0 R + /Size 7 +>> +startxref +3061 +%%EOF \ No newline at end of file diff --git a/submodules/TelegramUI/Sources/ApplicationContext.swift b/submodules/TelegramUI/Sources/ApplicationContext.swift index 9687cfcc40..c263dda468 100644 --- a/submodules/TelegramUI/Sources/ApplicationContext.swift +++ b/submodules/TelegramUI/Sources/ApplicationContext.swift @@ -25,6 +25,7 @@ import AppLock import AccountUtils import ContextUI import TelegramCallsUI +import AuthorizationUI final class UnauthorizedApplicationContext { let sharedContext: SharedAccountContextImpl @@ -48,6 +49,7 @@ final class UnauthorizedApplicationContext { self.rootController = AuthorizationSequenceController(sharedContext: sharedContext, account: account, otherAccountPhoneNumbers: otherAccountPhoneNumbers, presentationData: presentationData, openUrl: sharedContext.applicationBindings.openUrl, apiId: apiId, apiHash: apiHash, authorizationCompleted: { authorizationCompleted?() }) + (self.rootController as NavigationController).statusBarHost = sharedContext.mainWindow?.statusBarHost authorizationCompleted = { [weak self] in self?.authorizationCompleted = true diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 75ef52957f..7f96e766fb 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -407,6 +407,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G private var raiseToListen: RaiseToListenManager? private var voicePlaylistDidEndTimestamp: Double = 0.0 + private weak var emojiTooltipController: TooltipController? private weak var sendingOptionsTooltipController: TooltipController? private weak var searchResultsTooltipController: TooltipController? private weak var messageTooltipController: TooltipController? @@ -828,6 +829,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.sendMessages([message]) }, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference, sourceNode, sourceRect in return self?.controllerInteraction?.sendSticker(fileReference, false, false, nil, false, sourceNode, sourceRect, nil) ?? false + } : nil, sendEmoji: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { text, attribute in + self?.controllerInteraction?.sendEmoji(text, attribute) } : nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in if let strongSelf = self { strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { entry in @@ -1949,6 +1952,29 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.sendMessages(transformedMessages) } return true + }, sendEmoji: { [weak self] text, attribute in + if let strongSelf = self { + strongSelf.interfaceInteraction?.insertText(NSAttributedString(string: text, attributes: [ChatTextInputAttributes.customEmoji: attribute])) + strongSelf.updateChatPresentationInterfaceState(interactive: true, { state in + return state.updatedInputMode({ _ in + return .text + }) + }) + + let _ = (ApplicationSpecificNotice.getEmojiTooltip(accountManager: strongSelf.context.sharedContext.accountManager) + |> deliverOnMainQueue).start(next: { count in + guard let strongSelf = self else { + return + } + if count < 2 { + let _ = ApplicationSpecificNotice.incrementEmojiTooltip(accountManager: strongSelf.context.sharedContext.accountManager).start() + + Queue.mainQueue().after(0.5, { + strongSelf.displayEmojiTooltip() + }) + } + }) + } }, sendGif: { [weak self] fileReference, sourceView, sourceRect, silentPosting, schedule in if let strongSelf = self { if let _ = strongSelf.presentationInterfaceState.slowmodeState, strongSelf.presentationInterfaceState.subject != .scheduledMessages { @@ -15953,6 +15979,26 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) } + private func displayEmojiTooltip() { + guard let rect = self.chatDisplayNode.frameForEmojiButton(), self.effectiveNavigationController?.topViewController === self else { + return + } + self.emojiTooltipController?.dismiss() + let tooltipController = TooltipController(content: .text(self.presentationData.strings.Conversation_EmojiTooltip), baseFontSize: self.presentationData.listsFontSize.baseDisplaySize, timeout: 3.0, dismissByTapOutside: true, dismissImmediatelyOnLayoutUpdate: true, padding: 2.0) + self.emojiTooltipController = tooltipController + tooltipController.dismissed = { [weak self, weak tooltipController] _ in + if let strongSelf = self, let tooltipController = tooltipController, strongSelf.emojiTooltipController === tooltipController { + strongSelf.emojiTooltipController = nil + } + } + self.present(tooltipController, in: .window(.root), with: TooltipControllerPresentationArguments(sourceNodeAndRect: { [weak self] in + if let strongSelf = self { + return (strongSelf.chatDisplayNode, rect.offsetBy(dx: 0.0, dy: -3.0)) + } + return nil + })) + } + private func displayChecksTooltip() { self.checksTooltipController?.dismiss() @@ -15993,6 +16039,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } private func dismissAllTooltips() { + self.emojiTooltipController?.dismiss() self.sendingOptionsTooltipController?.dismiss() self.searchResultsTooltipController?.dismiss() self.messageTooltipController?.dismiss() @@ -16750,7 +16797,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.chatDisplayNode.dismissTextInput() let presentationData = self.presentationData - let controller = StickerPackScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mainStickerPack: packReference, stickerPacks: Array(references), parentNavigationController: self.effectiveNavigationController, actionPerformed: { [weak self] actions in + let controller = StickerPackScreen(context: self.context, updatedPresentationData: self.updatedPresentationData, mainStickerPack: packReference, stickerPacks: Array(references), parentNavigationController: self.effectiveNavigationController, sendEmoji: canSendMessagesToChat(self.presentationInterfaceState) ? { [weak self] text, attribute in + if let strongSelf = self { + strongSelf.controllerInteraction?.sendEmoji(text, attribute) + } + } : nil, actionPerformed: { [weak self] actions in guard let strongSelf = self else { return } diff --git a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift index eaa2413067..f8a7a28af9 100644 --- a/submodules/TelegramUI/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatControllerInteraction.swift @@ -13,6 +13,7 @@ import ChatInterfaceState import UndoUI import TelegramPresentationData import ChatPresentationInterfaceState +import TextFormat struct ChatInterfaceHighlightedState: Equatable { let messageStableId: UInt32 @@ -74,6 +75,7 @@ public final class ChatControllerInteraction { let sendCurrentMessage: (Bool) -> Void let sendMessage: (String) -> Void let sendSticker: (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?) -> Bool + let sendEmoji: (String, ChatTextInputTextCustomEmojiAttribute) -> Void let sendGif: (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool) -> Bool let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool, Bool) -> Void @@ -179,6 +181,7 @@ public final class ChatControllerInteraction { sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, Bool, String?, Bool, UIView, CGRect, CALayer?) -> Bool, + sendEmoji: @escaping (String, ChatTextInputTextCustomEmojiAttribute) -> Void, sendGif: @escaping (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool, Bool) -> Void, @@ -267,6 +270,7 @@ public final class ChatControllerInteraction { self.sendCurrentMessage = sendCurrentMessage self.sendMessage = sendMessage self.sendSticker = sendSticker + self.sendEmoji = sendEmoji self.sendGif = sendGif self.sendBotContextResultAsGif = sendBotContextResultAsGif self.requestMessageActionCallback = requestMessageActionCallback diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 1243c3130f..0659b56597 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -2563,6 +2563,15 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { return nil } + func frameForEmojiButton() -> CGRect? { + if let textInputPanelNode = self.textInputPanelNode, self.inputPanelNode === textInputPanelNode { + return textInputPanelNode.frameForEmojiButton().flatMap { + return $0.offsetBy(dx: textInputPanelNode.frame.minX, dy: textInputPanelNode.frame.minY) + } + } + return nil + } + var isTextInputPanelActive: Bool { return self.inputPanelNode is ChatTextInputPanelNode } diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift index eb66759d35..3a351f1757 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsControllerNode.swift @@ -225,7 +225,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, callPeer: { peerId, isVideo in self?.controllerInteraction?.callPeer(peerId, isVideo) }, enqueueMessage: { _ in - }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { signal, media in + }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { signal, media in if let strongSelf = self { strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { messageId in if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { @@ -261,7 +261,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, activateMessagePinch: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in - }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _ in return false }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in + }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in self?.openUrl(url) }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index 6f09bf8141..39a2e22322 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -3467,6 +3467,15 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate { } return nil } + + func frameForEmojiButton() -> CGRect? { + for (item, button) in self.accessoryItemButtons { + if case let .input(_, inputMode) = item, case .emoji = inputMode { + return button.frame.insetBy(dx: 0.0, dy: 6.0) + } + } + return nil + } func makeSnapshotForTransition() -> ChatMessageTransitionNode.Source.TextInput? { guard let backgroundImage = self.transparentTextInputBackgroundImage else { diff --git a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift index 53c2385a20..486a15d938 100644 --- a/submodules/TelegramUI/Sources/DrawingStickersScreen.swift +++ b/submodules/TelegramUI/Sources/DrawingStickersScreen.swift @@ -112,7 +112,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode { return false }, openPeer: { _, _, _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _, _ in }, openMessageReactionContextMenu: { _, _, _, _ in }, updateMessageReaction: { _, _ in }, activateMessagePinch: { _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, navigateToMessageStandalone: { _ in - }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, _, _, node, rect, _ in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + }, tapMessage: nil, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { fileReference, _, _, _, _, node, rect, _ in return selectStickerImpl?(fileReference, node, rect) ?? false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in }, presentControllerInCurrent: { _, _ in }, navigationController: { diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index a57005050b..4469c3d2c1 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -78,7 +78,7 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { params.navigationController?.pushViewController(controller) return true case let .stickerPack(reference): - let controller = StickerPackScreen(context: params.context, updatedPresentationData: params.updatedPresentationData, mainStickerPack: reference, stickerPacks: [reference], parentNavigationController: params.navigationController, sendSticker: params.sendSticker, actionPerformed: { actions in + let controller = StickerPackScreen(context: params.context, updatedPresentationData: params.updatedPresentationData, mainStickerPack: reference, stickerPacks: [reference], parentNavigationController: params.navigationController, sendSticker: params.sendSticker, sendEmoji: params.sendEmoji, actionPerformed: { actions in let presentationData = params.context.sharedContext.currentPresentationData.with { $0 } if actions.count > 1, let first = actions.first { diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index deabae37bc..199bbd74af 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -82,6 +82,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _ in return false + }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in @@ -282,7 +283,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu if let location = strongSelf.playlistLocation as? PeerMessagesPlaylistLocation, case let .custom(messages, _, loadMore) = location { playlistLocation = .custom(messages: messages, at: id, loadMore: loadMore) } - return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation)) + return strongSelf.context.sharedContext.openChatMessage(OpenChatMessageParams(context: strongSelf.context, chatLocation: nil, chatLocationContextHolder: nil, message: message, standalone: false, reverseMessageGalleryOrder: false, navigationController: nil, dismissInput: { }, present: { _, _ in }, transitionNode: { _, _ in return nil }, addToTransitionSurface: { _ in }, openUrl: { _ in }, openPeer: { _, _ in }, callPeer: { _, _ in }, enqueueMessage: { _ in }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, playlistLocation: playlistLocation)) } return false } diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index ebff935be5..cf8fefbf53 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -1759,8 +1759,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate private let archivedPacks = Promise<[ArchivedStickerPackItem]?>() private let blockedPeers = Promise(nil) private let hasTwoStepAuth = Promise(nil) - private let twoStepAuthData = Promise(nil) - private let hasPassport = Promise(false) + private let twoStepAccessConfiguration = Promise(nil) + private let twoStepAuthData = Promise(nil) private let supportPeerDisposable = MetaDisposable() private let tipsPeerDisposable = MetaDisposable() private let cachedFaq = Promise(nil) @@ -2271,6 +2271,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _ in return false + }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in @@ -3030,21 +3031,28 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate )) self.privacySettings.set(.single(nil) |> then(context.engine.privacy.requestAccountPrivacySettings() |> map(Optional.init))) self.archivedPacks.set(.single(nil) |> then(context.engine.stickers.archivedStickerPacks() |> map(Optional.init))) - self.twoStepAuthData.set(.single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() + self.twoStepAccessConfiguration.set(.single(nil) |> then(context.engine.auth.twoStepVerificationConfiguration() |> map { value -> TwoStepVerificationAccessConfiguration? in return TwoStepVerificationAccessConfiguration(configuration: value, password: nil) })) - self.hasPassport.set(.single(false) |> then(context.engine.auth.twoStepAuthData() - |> map { value -> Bool in - return value.hasSecretValues + + self.twoStepAuthData.set(.single(nil) + |> then( + context.engine.auth.twoStepAuthData() + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + )) + + let hasPassport = self.twoStepAuthData.get() + |> map { data -> Bool in + return data?.hasSecretValues ?? false } - |> `catch` { _ -> Signal in - return .single(false) - })) self.cachedFaq.set(.single(nil) |> then(cachedFaqInstantPage(context: self.context) |> map(Optional.init))) - screenData = peerInfoScreenSettingsData(context: context, peerId: peerId, accountsAndPeers: self.accountsAndPeers.get(), activeSessionsContextAndCount: self.activeSessionsContextAndCount.get(), notificationExceptions: self.notificationExceptions.get(), privacySettings: self.privacySettings.get(), archivedStickerPacks: self.archivedPacks.get(), hasPassport: self.hasPassport.get()) + screenData = peerInfoScreenSettingsData(context: context, peerId: peerId, accountsAndPeers: self.accountsAndPeers.get(), activeSessionsContextAndCount: self.activeSessionsContextAndCount.get(), notificationExceptions: self.notificationExceptions.get(), privacySettings: self.privacySettings.get(), archivedStickerPacks: self.archivedPacks.get(), hasPassport: hasPassport) self.headerNode.displayCopyContextMenu = { [weak self] node, copyPhone, copyUsername in @@ -3404,7 +3412,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate }, callPeer: { peerId, isVideo in //self?.controllerInteraction?.callPeer(peerId) }, enqueueMessage: { _ in - }, sendSticker: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, actionInteraction: GalleryControllerActionInteraction(openUrl: { [weak self] url, concealed in + }, sendSticker: nil, sendEmoji: nil, setupTemporaryHiddenMedia: { _, _, _ in }, chatAvatarHiddenMedia: { _, _ in }, actionInteraction: GalleryControllerActionInteraction(openUrl: { [weak self] url, concealed in if let strongSelf = self { strongSelf.openUrl(url: url, concealed: false, external: false) } @@ -6369,13 +6377,16 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate |> take(1) |> deliverOnMainQueue).start(next: { [weak self] blockedPeersContext, hasTwoStepAuth in if let strongSelf = self { + let loginEmailPattern = strongSelf.twoStepAuthData.get() |> map { data -> String? in + return data?.loginEmailPattern + } push(privacyAndSecurityController(context: strongSelf.context, initialSettings: settings.privacySettings, updatedSettings: { [weak self] settings in self?.privacySettings.set(.single(settings)) }, updatedBlockedPeers: { [weak self] blockedPeersContext in self?.blockedPeers.set(.single(blockedPeersContext)) }, updatedHasTwoStepAuth: { [weak self] hasTwoStepAuthValue in self?.hasTwoStepAuth.set(.single(hasTwoStepAuthValue)) - }, focusOnItemTag: nil, activeSessionsContext: settings.activeSessionsContext, webSessionsContext: settings.webSessionsContext, blockedPeersContext: blockedPeersContext, hasTwoStepAuth: hasTwoStepAuth)) + }, focusOnItemTag: nil, activeSessionsContext: settings.activeSessionsContext, webSessionsContext: settings.webSessionsContext, blockedPeersContext: blockedPeersContext, hasTwoStepAuth: hasTwoStepAuth, loginEmailPattern: loginEmailPattern)) } }) } @@ -6883,7 +6894,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } }) } - }, resolvedFaqUrl: self.cachedFaq.get(), exceptionsList: .single(settings.notificationExceptions), archivedStickerPacks: .single(settings.archivedStickerPacks), privacySettings: .single(settings.privacySettings), hasTwoStepAuth: self.hasTwoStepAuth.get(), twoStepAuthData: self.twoStepAuthData.get(), activeSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.0 }, webSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.2 }), cancel: { [weak self] in + }, resolvedFaqUrl: self.cachedFaq.get(), exceptionsList: .single(settings.notificationExceptions), archivedStickerPacks: .single(settings.archivedStickerPacks), privacySettings: .single(settings.privacySettings), hasTwoStepAuth: self.hasTwoStepAuth.get(), twoStepAuthData: self.twoStepAccessConfiguration.get(), activeSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.0 }, webSessionsContext: self.activeSessionsContextAndCount.get() |> map { $0?.2 }), cancel: { [weak self] in self?.deactivateSearch() }) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 97dfa1c9e3..758a158372 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1281,7 +1281,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { tapMessage?(message) }, clickThroughMessage: { clickThroughMessage?() - }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _ in return false }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in + }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _ in return false }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in @@ -1510,6 +1510,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { public func makeStickerPackScreen(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)?, mainStickerPack: StickerPackReference, stickerPacks: [StickerPackReference], parentNavigationController: NavigationController?, sendSticker: ((FileMediaReference, UIView, CGRect) -> Bool)?) -> ViewController { return StickerPackScreen(context: context, updatedPresentationData: updatedPresentationData, mainStickerPack: mainStickerPack, stickerPacks: stickerPacks, parentNavigationController: parentNavigationController, sendSticker: sendSticker) } + + public func makeProxySettingsController(sharedContext: SharedAccountContext, account: UnauthorizedAccount) -> ViewController { + return proxySettingsController(accountManager: sharedContext.accountManager, postbox: account.postbox, network: account.network, mode: .modal, presentationData: sharedContext.currentPresentationData.with { $0 }, updatedPresentationData: sharedContext.presentationData) + } } private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {